From 6a85efb00a3bbcce0bb645b1d81789c7862b9162 Mon Sep 17 00:00:00 2001
From: Mattsson <111893710+mattssonn@users.noreply.github.com>
Date: Wed, 2 Sep 2026 14:42:39 +0200
Subject: [PATCH] feat(mcp): allowlist Grok's connector callback and document
the Grok path (#2158)
* feat(mcp): allowlist Grok's connector callback and document the Grok path
Grok custom connectors self-register through /api/mcp-oauth/register with
redirect_uri https://grok.com/connectors-oauth-exchange-code/, which the
built-in allowlist rejected with invalid_redirect_uri before consent. Add
the callback as an exact-path BUILT_IN_PATTERNS entry (trailing slash
optional, no prefix) with provider 'grok', named "Grok (xAI)" on the
consent page. Tests: accept, foreign-host and other-path rejection,
provider mapping, and a register route test for the Grok DCR shape.
Surface Grok next to ChatGPT: a "Using Grok?" side door on the onboarding
Claude step (one side door open at a time, telemetry step grok), a Grok row
under "Other clients" in the API & MCP settings tab using ?client=grok, and
sv/en strings for both. Docs: mcp-server rule, ARCHITECTURE, README,
registry entry (install section), DECISIONS.
Co-Authored-By: Claude Fable 5.1
Claude-Session: https://claude.ai/code/session_01EGbspj3hiNqvqTWZqdwysa
Signed-off-by: Emil
* fix(mcp): cite X Corp's published Grok callback, test the consent label
Review pass on #2158: the allowlist comment and DECISIONS entry claimed
xAI publishes no callback and the value came from a live observation; X
Corp lists https://grok.com/connectors-oauth-exchange-code/ as the "Grok
(web)" redirect URL at docs.x.com/x-ads-api/mcp, and grok.com serves the
path itself (slash form 308s to no-slash on the same origin). Reworded
both to cite that. Adds the consent-page test for "Grok (xAI)" next to
the ChatGPT one and a JSDoc on the onboarding side-door toggle.
Co-Authored-By: Claude Fable 5.1
Claude-Session: https://claude.ai/code/session_01EGbspj3hiNqvqTWZqdwysa
Signed-off-by: Emil
---------
Signed-off-by: Emil
Co-authored-by: Claude Fable 5.1
---
.claude/rules/mcp-server.md | 2 +-
ARCHITECTURE.md | 2 +-
DECISIONS.md | 1 +
README.md | 2 +-
.../authorize/__tests__/route.test.ts | 20 +++++++
app/api/mcp-oauth/authorize/route.ts | 2 +
.../register/__tests__/route.test.ts | 19 +++++++
components/onboarding/NewUserChecklist.tsx | 55 ++++++++++++-------
components/settings/ApiKeysPanel.tsx | 10 ++++
lib/auth/__tests__/oauth-allowlist.test.ts | 9 +++
lib/auth/oauth-allowlist.ts | 16 +++++-
messages/en.json | 7 ++-
messages/sv.json | 7 ++-
registry/entries/gnubok-mcp.mdx | 11 +++-
14 files changed, 131 insertions(+), 32 deletions(-)
diff --git a/.claude/rules/mcp-server.md b/.claude/rules/mcp-server.md
index eafe5d68..f21caf40 100644
--- a/.claude/rules/mcp-server.md
+++ b/.claude/rules/mcp-server.md
@@ -11,7 +11,7 @@ Accounted exposes its bookkeeping engine as an MCP server for Claude Desktop/Cod
**MCP extension** (`extensions/general/mcp-server/`): 150+ tools (count `name: 'gnubok_` in `server.ts`; docs say "150+", never an exact number, because it drifts) covering transactions, categorization, customers/suppliers, invoices, accounts, fiscal periods, reports (trial balance, GL, BS, IS, AR/supplier ledger, VAT, KPI), reconciliation, salary runs, AGI, year-end, document upload, and loadable skills. JSON-RPC 2.0. Endpoint: `/api/extensions/ext/mcp-server/mcp`.
-**OAuth 2.1** for Claude and ChatGPT connectors: `.well-known/oauth-protected-resource` + `.well-known/oauth-authorization-server` discovery; `/api/mcp-oauth/authorize`, `/token` (PKCE), `/register`. Stateless AES-256-GCM auth codes (`lib/auth/oauth-codes.ts`). Single-use via `oauth_used_codes`. Allowlist: `claude.ai/api/*`, `claude.com/api/*`, `chatgpt.com/connector/oauth/*`, `chatgpt.com/connector_platform_oauth_redirect`, `localhost`.
+**OAuth 2.1** for Claude, ChatGPT and Grok connectors: `.well-known/oauth-protected-resource` + `.well-known/oauth-authorization-server` discovery; `/api/mcp-oauth/authorize`, `/token` (PKCE), `/register`. Stateless AES-256-GCM auth codes (`lib/auth/oauth-codes.ts`). Single-use via `oauth_used_codes`. Allowlist: `claude.ai/api/*`, `claude.com/api/*`, `chatgpt.com/connector/oauth/*`, `chatgpt.com/connector_platform_oauth_redirect`, `grok.com/connectors-oauth-exchange-code/` (exact), `localhost`.
**npm packages**: `packages/accounted-mcp` is the Accounted stdio-to-HTTP bridge for new installs. `packages/gnubok-mcp` is the permanent compatibility package for existing configurations.
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 6d829d2e..4f736a24 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -112,7 +112,7 @@ transactions, draft vouchers, reconcile periods, generate reports and
declarations.
- Authentication uses scoped API keys (stored as SHA-256 hashes, rate limited
- per key). Claude and ChatGPT connectors instead authenticate with OAuth 2.1
+ per key). Claude, ChatGPT and Grok connectors instead authenticate with OAuth 2.1
(PKCE; `app/api/mcp-oauth/{authorize,register,token}` plus the
`.well-known` discovery documents), which mints a scoped API key behind the
scenes. Authentication is lazy: a client can connect, list tools, and call a
diff --git a/DECISIONS.md b/DECISIONS.md
index e4a61c5b..d100e25a 100644
--- a/DECISIONS.md
+++ b/DECISIONS.md
@@ -1483,6 +1483,7 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and
[2026-09-01] ENABLE_BANKING_SANDBOX removed from the enable-banking manifest and the index.ts header (#2131): the variable was declared as optional but never read anywhere; sandbox vs production is decided by ENABLE_BANKING_API_URL (api.tilisy.com vs api.enablebanking.com, api-client.ts derives isSandbox from the host). A dead variable declared in the manifest is what the self-hosting docs would otherwise have copied. The manifest now lists the two optional variables the code actually reads (API_URL, PSU_TYPE); the _PRODUCTION aliases stay undeclared on purpose, they are a hosted Vercel convention, not an operator contract.
[2026-09-02] Repo-wide bloat sweep (chore/bloat-sweep-2026-09): removed dead files/exports/types/i18n namespaces and deduplicated byte-identical helpers into canonical homes (lib/utils chunk/sleep/utcDateStamp, lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format, lib/pdf/number-text pdfAmount/formatDateSv, lib/browser/panel-request, lib/api/v1/body + v1ValidationError, lib/bookkeeping/booking-template-schemas). Deliberately NOT done: naive Math.round(x*100)/100 helpers were not swapped for roundOre (behaviour change at half-ore values, ratchet campaign owns it); lib/bokslut/rounding.ts shim kept because money.test.ts asserts the back-compat re-export; text-based v1 body parsers (empty body allowed) kept inline because readV1JsonBody has different empty-body semantics; the four HTTP endpoints with no first-party caller (skatteverket agi/underlag + agi/sparad DELETE, invoice-inbox items/:id/history, mail connections/backfill) stay because removing a reachable endpoint is a surface change; VacationBalanceCard deleted as unreachable since #1130, the v1/MCP vacation-year-close routes stay.
[2026-09-01] PR #2130 security-scan round: the register's djuplank is validated (https + skatteverket.se host) before it is returned or navigated to, since the settings page follows it; a contested org number now WITHDRAWS an already-recorded grant nightly (not only blocks new ones), outside the downgrade guards on purpose. NOT done: proof of org-number ownership (Bolagsverket firmatecknare / BankID) before any ombud grant; the org number is tenant-editable across the product (AGI, invoices, årsredovisning) and binding it to a verified identity is a product decision for Emil, tracked as a follow-up rather than declined.
+[2026-09-02] Grok custom connectors are allowlisted by the exact callback https://grok.com/connectors-oauth-exchange-code/ (trailing slash optional), not a grok.com prefix: the value is published by X Corp at docs.x.com/x-ads-api/mcp ("Grok (web)" redirect URL) and grok.com serves the path itself (slash form 308s to no-slash on the same origin), and a prefix would let any future grok.com path receive authorization codes. Grok gets side doors next to ChatGPT (onboarding) and a row under "Other clients" (settings); Claude stays the visual primary per the 2026-08-27 founder call. No new client marker plumbing: the settings URL uses the existing ?client= param with value grok.
[2026-09-02] Viewer write gate as ONE table-level trigger (enforce_company_writer_role) instead of re-emitting 15 SECURITY DEFINER bodies and ~130 policies: keyed on the JWT role claim so it fires inside definer functions too; no-op for service_role and trigger cascades. agent_conversations/agent_messages and telemetry tables deliberately excluded.
[2026-09-02] Posting-integrity guards key on current_user IN ('anon','authenticated'), not the JWT claim: inside SECURITY DEFINER RPCs current_user is the definer, so commit_journal_entry, SIE import, storno and rättelse keep working while direct PostgREST manipulation of posted vouchers is blocked. Residual: a direct draft->posted flip may still reuse an unused number below the sequence high-water mark.
[2026-09-02] Kept lib/auth/rate-limit-http.ts fail-open on hosted with an error-level log instead of failing closed: production has no Upstash configured, so fail-closed would 503 every rate-limited route until the env is set. Operator action: set UPSTASH_REDIS_REST_URL/TOKEN.
diff --git a/README.md b/README.md
index 1952f2aa..fd3cb800 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Open-source Swedish accounting software for sole traders (enskild firma) and lim
- **Skattekonto** -- Tax account transactions synced from Skatteverket or imported from statement files, linked to the booked 1630 movements for reconciliation
- **Document archive** -- SHA-256 integrity, 7-year retention enforcement, full archive ZIP export
- **SIE import/export** -- Standard Swedish accounting interchange format
-- **Agent access (MCP)** -- 150+ bookkeeping tools over the Model Context Protocol, with scoped API keys and staged approvals
+- **Agent access (MCP)** -- 150+ bookkeeping tools over the Model Context Protocol, with scoped API keys and staged approvals; connects to Claude, ChatGPT and Grok over OAuth 2.1
- **Claude connector and plugin** -- Connect Claude.ai or Claude Code over OAuth 2.1 and install approval-gated workflow skills (`/accounted:bookkeep`, `/accounted:vat`, `/accounted:year-end`, ...) from [claude-plugin/](claude-plugin/README.md)
- **Extension system** -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more
diff --git a/app/api/mcp-oauth/authorize/__tests__/route.test.ts b/app/api/mcp-oauth/authorize/__tests__/route.test.ts
index 9e326464..115a0d2f 100644
--- a/app/api/mcp-oauth/authorize/__tests__/route.test.ts
+++ b/app/api/mcp-oauth/authorize/__tests__/route.test.ts
@@ -40,6 +40,7 @@ import { GET, POST } from '../route'
const CLAUDE: RedirectUriResolution = { allowed: true, kind: 'built_in', provider: 'claude' }
const CHATGPT: RedirectUriResolution = { allowed: true, kind: 'built_in', provider: 'chatgpt' }
+const GROK: RedirectUriResolution = { allowed: true, kind: 'built_in', provider: 'grok' }
const REGISTERED: RedirectUriResolution = {
allowed: true,
kind: 'registered',
@@ -364,6 +365,25 @@ describe('client identity on the consent page', () => {
expect(html).toContain('chatgpt.com')
})
+ it('names Grok as a verified client for the grok.com callback', async () => {
+ mocks.resolveRedirectUri.mockResolvedValue(GROK)
+ const html = await (
+ await GET(
+ new Request(
+ buildAuthorizeUrl({
+ ...params,
+ redirect_uri: 'https://grok.com/connectors-oauth-exchange-code/',
+ }),
+ ),
+ )
+ ).text()
+
+ expect(html).toContain('Grok (xAI)')
+ expect(html).toContain('Verifierad')
+ expect(html).toContain('grok.com')
+ expect(html).not.toContain('En extern applikation')
+ })
+
it('shows client_name and redirect host for a DB-registered client, never marked verified', async () => {
mocks.resolveRedirectUri.mockResolvedValue(REGISTERED)
const html = await (
diff --git a/app/api/mcp-oauth/authorize/route.ts b/app/api/mcp-oauth/authorize/route.ts
index a83458d2..068993ec 100644
--- a/app/api/mcp-oauth/authorize/route.ts
+++ b/app/api/mcp-oauth/authorize/route.ts
@@ -1092,6 +1092,8 @@ function describeClient(
return { name: 'Claude (Anthropic)', tag: 'Verifierad', verified: true }
case 'chatgpt':
return { name: 'ChatGPT (OpenAI)', tag: 'Verifierad', verified: true }
+ case 'grok':
+ return { name: 'Grok (xAI)', tag: 'Verifierad', verified: true }
case 'local':
return { name: 'Lokal utveckling (localhost)', tag: 'Din egen dator', verified: false }
}
diff --git a/app/api/mcp-oauth/register/__tests__/route.test.ts b/app/api/mcp-oauth/register/__tests__/route.test.ts
index d13c884a..df8cdd15 100644
--- a/app/api/mcp-oauth/register/__tests__/route.test.ts
+++ b/app/api/mcp-oauth/register/__tests__/route.test.ts
@@ -51,6 +51,25 @@ describe('POST /api/mcp-oauth/register', () => {
expect(response.status).toBe(201)
})
+ it('accepts registration with the grok.com connector callback', async () => {
+ const response = await POST(createRequest({
+ client_name: 'Grok',
+ redirect_uris: ['https://grok.com/connectors-oauth-exchange-code/'],
+ token_endpoint_auth_method: 'none',
+ }))
+ expect(response.status).toBe(201)
+ const body = await response.json()
+ expect(body.redirect_uris).toEqual(['https://grok.com/connectors-oauth-exchange-code/'])
+ expect(body.token_endpoint_auth_method).toBe('none')
+ })
+
+ it('rejects other grok.com paths', async () => {
+ const response = await POST(createRequest({
+ redirect_uris: ['https://grok.com/oauth/callback'],
+ }))
+ expect(response.status).toBe(400)
+ })
+
it('rejects registration with disallowed redirect_uris', async () => {
const response = await POST(createRequest({
redirect_uris: ['https://evil.com/callback'],
diff --git a/components/onboarding/NewUserChecklist.tsx b/components/onboarding/NewUserChecklist.tsx
index 26adc689..680972a9 100644
--- a/components/onboarding/NewUserChecklist.tsx
+++ b/components/onboarding/NewUserChecklist.tsx
@@ -47,6 +47,12 @@ interface NewUserChecklistProps {
sieSweep?: { auto_linked: number; suggested: number; unmatched: number; errors: number } | null
}
+/** Clients that get a collapsed "Using X?" side door under the Claude step.
+ * Each value keys the i18n strings step_claude__link / _steps and the
+ * telemetry step name. Order is display order. */
+const SIDE_DOORS = ['chatgpt', 'grok'] as const
+type SideDoor = (typeof SIDE_DOORS)[number]
+
/**
* Activation funnel events, mirroring the one existing product-event site
* (lib/support/submit-feedback.ts): guarded, try/caught, no PII in
@@ -108,9 +114,9 @@ export default function NewUserChecklist({
// effect and re-raised the error toast forever. The next visit tries once
// more from server truth.
const completeRejectedRef = useRef(false)
- // The ChatGPT side door on the Claude step: collapsed by default so the
- // one-click Claude path stays the visual primary.
- const [chatGptOpen, setChatGptOpen] = useState(false)
+ // The ChatGPT and Grok side doors on the Claude step: collapsed by default
+ // so the one-click Claude path stays the visual primary; at most one open.
+ const [sideDoor, setSideDoor] = useState(null)
const [serverUrlCopied, setServerUrlCopied] = useState(false)
const hasMigration = ENABLED_EXTENSION_IDS.has('arcim-migration')
@@ -258,12 +264,16 @@ export default function NewUserChecklist({
'noopener',
)
}
- // ChatGPT has no add-connector deep link (the user pastes the server URL
- // into Developer mode manually), so the side door copies the URL instead.
- const toggleChatGpt = () => {
- setChatGptOpen((open) => {
- if (!open) captureSetup('onboarding_setup_step_started', { step: 'chatgpt' })
- return !open
+ /**
+ * Open one side door (closing any other) or close it when it is already
+ * open. Neither ChatGPT nor Grok has an add-connector deep link (the user
+ * pastes the server URL into the client manually), so the side doors copy
+ * the URL instead. Telemetry fires once per open, never on close.
+ */
+ const toggleSideDoor = (door: SideDoor) => {
+ setSideDoor((open) => {
+ if (open !== door) captureSetup('onboarding_setup_step_started', { step: door })
+ return open === door ? null : door
})
}
const copyServerUrl = async () => {
@@ -467,18 +477,23 @@ export default function NewUserChecklist({
{t('step_claude_guide_link')}
diff --git a/lib/auth/__tests__/oauth-allowlist.test.ts b/lib/auth/__tests__/oauth-allowlist.test.ts
index 23ed0de0..bb03eecf 100644
--- a/lib/auth/__tests__/oauth-allowlist.test.ts
+++ b/lib/auth/__tests__/oauth-allowlist.test.ts
@@ -19,6 +19,13 @@ describe('isBuiltInRedirectUri', () => {
['https://chatgpt.com/connector_platform_oauth_redirect/extra', false],
['https://chatgpt.com/other/path', false],
['https://chatgpt.com.evil.com/connector/oauth/x', false],
+ ['https://grok.com/connectors-oauth-exchange-code/', true],
+ ['https://grok.com/connectors-oauth-exchange-code', true],
+ ['https://grok.com/connectors-oauth-exchange-code/extra', false],
+ ['https://grok.com/connectors-oauth-exchange-code/?next=x', false],
+ ['https://grok.com/other/path', false],
+ ['https://grok.com.evil.com/connectors-oauth-exchange-code/', false],
+ ['http://grok.com/connectors-oauth-exchange-code/', false],
['http://localhost:3000/cb', true],
['http://localhost/cb', true],
['http://127.0.0.1:8080/cb', true],
@@ -37,6 +44,8 @@ describe('builtInRedirectProvider', () => {
['https://claude.com/api/oauth/callback', 'claude'],
['https://chatgpt.com/connector/oauth/abc123', 'chatgpt'],
['https://chatgpt.com/connector_platform_oauth_redirect', 'chatgpt'],
+ ['https://grok.com/connectors-oauth-exchange-code/', 'grok'],
+ ['https://grok.com/connectors-oauth-exchange-code/extra', null],
['http://localhost:3000/cb', 'local'],
['http://127.0.0.1:8080/cb', 'local'],
['https://claude-login.example/cb', null],
diff --git a/lib/auth/oauth-allowlist.ts b/lib/auth/oauth-allowlist.ts
index a1b249df..1df66d1f 100644
--- a/lib/auth/oauth-allowlist.ts
+++ b/lib/auth/oauth-allowlist.ts
@@ -14,25 +14,35 @@ import { scopeKind, type ApiKeyScope } from './scope-catalog'
/**
* Identity of a built-in client, derived from the redirect URI pattern that
* matched. Rendered on the consent page so the user can tell a real Claude /
- * ChatGPT connector from a look-alike registration.
+ * ChatGPT / Grok connector from a look-alike registration.
*/
-export type BuiltInProvider = 'claude' | 'chatgpt' | 'local'
+export type BuiltInProvider = 'claude' | 'chatgpt' | 'grok' | 'local'
/**
* Built-in redirect URI patterns. These bypass the DB lookup entirely so
- * Claude's and ChatGPT's connectors keep working without seeded rows, and so
+ * the Claude, ChatGPT and Grok connectors keep working without seeded rows, and so
* local development never depends on having a registration.
*
* ChatGPT uses a per-connector-instance callback path
* (https://chatgpt.com/connector/oauth/{callback_id}) plus the legacy fixed
* callback for already-published apps; both are documented at
* developers.openai.com/apps-sdk/build/auth.
+ *
+ * Grok (grok.com custom connectors) registers itself through /register as a
+ * public client and sends a single fixed callback,
+ * https://grok.com/connectors-oauth-exchange-code/, published by X Corp as
+ * the "Grok (web)" redirect URL at docs.x.com/x-ads-api/mcp (xAI's own
+ * connector docs at docs.x.ai do not state it). grok.com serves the path
+ * itself: the slash form 308s to the no-slash form on the same origin, so
+ * both are accepted. Matched as an exact path, never a prefix, so a future
+ * grok.com path cannot ride on this entry.
*/
const BUILT_IN_PATTERNS: readonly { pattern: RegExp; provider: BuiltInProvider }[] = [
{ pattern: /^https:\/\/claude\.ai\/api\//, provider: 'claude' },
{ pattern: /^https:\/\/claude\.com\/api\//, provider: 'claude' },
{ pattern: /^https:\/\/chatgpt\.com\/connector\/oauth\//, provider: 'chatgpt' },
{ pattern: /^https:\/\/chatgpt\.com\/connector_platform_oauth_redirect$/, provider: 'chatgpt' },
+ { pattern: /^https:\/\/grok\.com\/connectors-oauth-exchange-code\/?$/, provider: 'grok' },
{ pattern: /^http:\/\/localhost(:\d+)?(\/|$)/, provider: 'local' },
{ pattern: /^http:\/\/127\.0\.0\.1(:\d+)?(\/|$)/, provider: 'local' },
]
diff --git a/messages/en.json b/messages/en.json
index bef56716..9b5694b7 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -1358,6 +1358,8 @@
"step_claude_chatgpt_steps": "In ChatGPT (Plus, Pro or Business): Settings → Apps → Advanced settings → turn on Developer mode. Choose Add custom connector, name it {appName} and paste the server address. Then start a new chat and ask for a walkthrough of your books.",
"step_claude_chatgpt_copy": "Copy server address",
"step_claude_chatgpt_copied": "Copied",
+ "step_claude_grok_link": "Using Grok?",
+ "step_claude_grok_steps": "On grok.com: Connectors → New Connector → Custom. Name it {appName}, paste the server address and sign in when Grok asks. Then start a new chat with the connector switched on and ask for a walkthrough of your books.",
"step_claude_expectation": "Claude lists the tools straight away. The first real question opens a login prompt: approve it and ask again.",
"step_claude_guide_link": "Guide: connect Claude step by step"
},
@@ -2600,10 +2602,11 @@
"connect_mcp_title": "Connect MCP client",
"connect_to_claude": "Connect to Claude",
"connect_to_claude_help": "Opens claude.ai with the URL prefilled. You review and confirm there; no API key needed. Your instance must be reachable from the internet. Claude lists the tools straight away; the first real question opens a login prompt, approve it and ask again.",
- "other_clients": "Other clients: Claude Code, Cursor, plugin",
+ "other_clients": "Other clients: Grok, Claude Code, Cursor, plugin",
"claude_ai_manual": "claude.ai (manual)",
- "works_with_ai": "Works with Claude, ChatGPT, and other AI assistants.",
+ "works_with_ai": "Works with Claude, ChatGPT, Grok, and other AI assistants.",
"claude_ai_instructions": "Go to Settings → Connectors → Add custom connector and paste the MCP server URL. You sign in via your {connectorName} account: no API key needed.",
+ "grok_instructions": "On grok.com go to Connectors → New Connector → Custom and paste the MCP server URL. Grok registers itself and opens the sign-in: no API key needed.",
"cursor_instructions": "Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (per project). Cursor does not read claude mcp add.",
"terminal_runs_browser_login": "Run in the terminal, sign in via the browser:",
"connect_with_api_key": "Connect with an API key",
diff --git a/messages/sv.json b/messages/sv.json
index c2918999..e9e0cb63 100644
--- a/messages/sv.json
+++ b/messages/sv.json
@@ -1358,6 +1358,8 @@
"step_claude_chatgpt_steps": "I ChatGPT (Plus, Pro eller Business): Inställningar → Appar → Avancerade inställningar → slå på Utvecklarläge. Välj Lägg till anpassad connector, namnge den {appName} och klistra in serveradressen. Starta sedan en ny chatt och be om en genomgång av bokföringen.",
"step_claude_chatgpt_copy": "Kopiera serveradress",
"step_claude_chatgpt_copied": "Kopierad",
+ "step_claude_grok_link": "Använder du Grok?",
+ "step_claude_grok_steps": "På grok.com: Connectors → New Connector → Custom. Namnge den {appName}, klistra in serveradressen och logga in när Grok frågar. Starta sedan en ny chatt med connectorn påslagen och be om en genomgång av bokföringen.",
"step_claude_expectation": "Claude listar verktygen direkt. Första riktiga frågan öppnar en inloggning: godkänn den och ställ frågan igen.",
"step_claude_guide_link": "Guide: anslut Claude steg för steg"
},
@@ -2600,10 +2602,11 @@
"connect_mcp_title": "Anslut MCP-klient",
"connect_to_claude": "Anslut till Claude",
"connect_to_claude_help": "Öppnar claude.ai med adressen ifylld. Du granskar och godkänner där; ingen API-nyckel behövs. Kräver att din instans går att nå från internet. Claude listar verktygen direkt; första riktiga frågan öppnar en inloggning, godkänn den och ställ frågan igen.",
- "other_clients": "Andra klienter: Claude Code, Cursor, plugin",
+ "other_clients": "Andra klienter: Grok, Claude Code, Cursor, plugin",
"claude_ai_manual": "claude.ai (manuellt)",
- "works_with_ai": "Fungerar med Claude, ChatGPT och andra AI-assistenter.",
+ "works_with_ai": "Fungerar med Claude, ChatGPT, Grok och andra AI-assistenter.",
"claude_ai_instructions": "Gå till Settings → Connectors → Add custom connector och klistra in MCP-serverns URL. Du loggas in via ditt {connectorName}-konto: ingen API-nyckel behövs.",
+ "grok_instructions": "På grok.com: gå till Connectors → New Connector → Custom och klistra in MCP-serverns URL. Grok registrerar sig själv och öppnar inloggningen: ingen API-nyckel behövs.",
"cursor_instructions": "Lägg till i ~/.cursor/mcp.json (globalt) eller .cursor/mcp.json (per projekt). Cursor läser inte claude mcp add.",
"terminal_runs_browser_login": "Kör i terminalen, loggar in via webbläsaren:",
"connect_with_api_key": "Anslut med API-nyckel",
diff --git a/registry/entries/gnubok-mcp.mdx b/registry/entries/gnubok-mcp.mdx
index 3c5879ac..8110a121 100644
--- a/registry/entries/gnubok-mcp.mdx
+++ b/registry/entries/gnubok-mcp.mdx
@@ -1,6 +1,6 @@
---
title: "accounted MCP-server"
-description: "MCP-server för svensk dubbel bokföring. Över 150 verktyg över bokföring, fakturor, leverantörer, moms, lön och rapporter. OAuth 2.1 med PKCE och dynamisk klientregistrering. Installeras som connector i Claude.ai, Claude Desktop, Cursor och Continue."
+description: "MCP-server för svensk dubbel bokföring. Över 150 verktyg över bokföring, fakturor, leverantörer, moms, lön och rapporter. OAuth 2.1 med PKCE och dynamisk klientregistrering. Installeras som connector i Claude.ai, ChatGPT, Grok, Claude Desktop, Cursor och Continue."
slug: "gnubok-mcp"
kind: "mcp"
author: "gnubok"
@@ -31,7 +31,7 @@ faq:
- q: "Hur funkar OAuth-flödet?"
a: "MCP-klienten upptäcker accounteds autentiseringsserver via `.well-known/oauth-protected-resource`. Klienten registrerar sig dynamiskt (RFC 7591) på `/api/mcp-oauth/register`, öppnar `/api/mcp-oauth/authorize` i webbläsaren, du loggar in på accounted och godkänner anslutningen, klienten utbyter authorization code mot en access token via `/api/mcp-oauth/token` med PKCE S256."
- q: "Vilka klienter funkar?"
- a: "Allt som följer MCP-spec 2026-02 och stödjer HTTP-transport med OAuth: Claude.ai (connectors), Claude Desktop (HTTP), Cursor, Continue, Codex. För stdio-bara klienter finns en lokal bro: `npx gnubok-mcp` med API-nyckel."
+ a: "Allt som följer MCP-spec 2026-02 och stödjer HTTP-transport med OAuth: Claude.ai (connectors), ChatGPT (Developer mode), Grok (custom connectors), Claude Desktop (HTTP), Cursor, Continue, Codex. För stdio-bara klienter finns en lokal bro: `npx gnubok-mcp` med API-nyckel."
- q: "Vilka scopes ger jag bort?"
a: "OAuth-flödet ger en enskild `mcp`-scope: 'denna agent får använda accounteds MCP'. Inom det är agentens åtkomst begränsad till samma rättigheter som ditt eget accounted-konto har. Du kan när som helst återkalla anslutningen i app.gnubok.se → Inställningar."
- q: "Får agenten skriva direkt till huvudboken?"
@@ -69,6 +69,13 @@ verktyg den får exponera.
3. Claude öppnar accounted-OAuth i webbläsaren. Logga in. Godkänn anslutningen.
4. Connectorn dyker upp i listan. Slå på för de chattar där du vill ha den aktiv.
+## Installera i Grok
+
+1. På grok.com: Connectors → New Connector → Custom.
+2. Ange URL: `https://app.gnubok.se/api/extensions/ext/mcp-server/mcp`.
+3. Grok registrerar sig själv och öppnar accounted-OAuth. Logga in. Godkänn anslutningen.
+4. Starta en ny chatt med connectorn påslagen.
+
## Installera i Claude Desktop
`claude_desktop_config.json` (`~/Library/Application Support/Claude/` på macOS):