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')}

- - {chatGptOpen && ( +
+ {SIDE_DOORS.map((door) => ( + + ))} +
+ {sideDoor && (

- {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 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):