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]
- {t('step_claude_chatgpt_steps', { appName })} + {t(`step_claude_${sideDoor}_steps`, { appName })}
Grok
++ {t.rich('grok_instructions', { + path: (chunks) => {chunks}, + })} +
+{t('claude_plugin_label')}
{t('claude_plugin_instructions')}
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~/.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 ~/.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):