* fix(mcp): explain the Claude-side steps after "Anslut till Claude" and tick the checklist on a real connection (#2133) Lazy auth is by design: Claude lists the tools before any sign-in and the first company-scoped call answers 401, which opens the Accounted sign-in. Nothing told the user, so a "connected" status with an unanswered first question read as a broken connection (Axel, Discord). - Settings -> API & MCP: one sentence of expectation under the button, and the step-by-step guide link moved from under two disclosures to directly under the button. - Docs (connect-claude / anslut-claude): new "What happens after you click" section for Path A covering the connector dialog, the tools appearing before sign-in, the first-call login + consent screen, "ask again", and the "Required when the server asks" auth setting that only the manual path mentioned. - Hem checklist step "Anslut till Claude": deep link now carries client=claude-connector like the settings button (claudeConnectorLink), the footnote carries the same expectation line plus the guide link, and the done-signal is an unrevoked api_keys row minted by the MCP OAuth token route (OAUTH_MCP_KEY_NAME) instead of the in-app AI-profile flag, which never meant "connected to Claude". - Tests: claudeStepDone with/without a key row, deep-link snapshot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L * fix(mcp): correct consent-page claims, stop the completion PATCH loop, count OAuth keys past RLS (#2133) Three skeptic refutations on PR #2147, fixed in one pass: - Docs (EN + SV): the consent page shows the company active in the app and pre-selects every scope for Claude's connector (founder decision 2026-08-26); it has no company picker and nothing to tick. Steps 3-4 of the new section, the "Read-only by default" paragraph above it, the sandbox note and the 10-minute test now describe Endast läs under Behörigheter instead. - Checklist completion: users with initial_setup_path NULL (skipped the books question, then imported) hit the route's "Välj först hur du vill komma igång" 400 and, with saving as an effect dependency, retried it forever with a toast. completionPatchBody() records path=migration when none was chosen, and a rejected PATCH is not retried within the session. - hasMcpKey: api_keys' SELECT policy is company-scoped, so the user client could not see companyless (NULL company_id) or archived-company keys and the step stayed open for the user who had just connected. The head count now runs through the service client with an explicit user_id filter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L * fix(mcp): surface a failed OAuth-key count and reserve the marker name (#2133) CodeRabbit round on PR #2147: - app/(dashboard)/page.tsx: a failed api_keys count answered count null, which claudeStepDone read as "never connected". Throw to the error boundary like the settings fetch does instead of guessing. - app/api/settings/api-keys: reject a hand-minted key named MCP-klient (OAuth) (400 VALIDATION_ERROR): that name is the marker the Hem checklist reads as "connected to Claude", so a manual key with it would tick the step without any connection. Test added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
143 lines
10 KiB
TypeScript
143 lines
10 KiB
TypeScript
export const CONNECT_CLAUDE_MD = `# Connect with Claude
|
|
|
|
> Talk to your bookkeeping. Connect Accounted to Claude (claude.ai, Claude Desktop, or Claude Code) and ask questions, categorise transactions, and prepare a momsdeklaration in plain language: every write still stages for your approval first.
|
|
|
|
_Den här sidan på svenska: [Anslut Claude](/docs/api/anslut-claude)._
|
|
|
|
Accounted ships an [MCP](https://modelcontextprotocol.io) server that exposes the full bookkeeping engine (150+ tools) to any MCP client. The endpoint is:
|
|
|
|
\`\`\`
|
|
https://app.accounted.se/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted
|
|
\`\`\`
|
|
|
|
There are three ways to connect, depending on your client. All three reach the same tools and the same approval model: read tools answer immediately, write tools (categorise, mark paid, create voucher, year-end) **stage a pending operation** that you confirm in chat or in the **/pending** web UI before anything is booked.
|
|
|
|
## Path A: claude.ai or Claude Desktop (one click)
|
|
|
|
**[→ Connect Accounted to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Accounted&connectorUrl=https%3A%2F%2Fapp.accounted.se%2Fapi%2Fextensions%2Fext%2Fmcp-server%2Fmcp%3Ftool_namespace%3Daccounted%26client%3Dclaude-connector)**
|
|
|
|
The link opens claude.ai with the connector name and URL already filled in. You review the values and confirm; the link only prefills the dialog, it grants nothing on its own. No API key to manage.
|
|
|
|
**You do not need an Accounted account yet.** The connector works as soon as it is added: the server answers the handshake and the documentation tools without credentials, and the first company-scoped call opens the Accounted sign-in, where a new user creates the account (BankID or e-mail + 2FA).
|
|
|
|
**All permissions pre-selected, every write still staged.** The consent page grants the full scope set with one click. Expand **Behörigheter** and choose **Endast läs** to connect read-only (list invoices, read reports, compute VAT): a reviewer can do that while you keep a write-enabled connection for daily work. Whatever the scopes, write tools (create invoice, categorise, book vouchers, run year-end) only stage a pending operation that you confirm before anything is booked, and the grant can be revoked under Settings → API & MCP.
|
|
|
|
#### What happens after you click
|
|
|
|
The rest of the setup happens on Claude's side, in this order:
|
|
|
|
1. **The connector dialog.** claude.ai opens **Add custom connector** with the name and URL filled in. Check the URL and click **Add**. If the dialog asks about authentication, choose **"Required when the server asks"**, not the auto-detected "None": the server does not demand a login at connect time, so "None" looks right but blocks the sign-in in step 3. Claude Desktop shows the same dialog under Settings → Connectors.
|
|
2. **The tools appear straight away.** The connector shows as connected and Claude lists the Accounted tools before you have signed in. That is by design: the handshake and the documentation tools need no account.
|
|
3. **The first real question opens the sign-in.** Ask something about your books, for example *"Which company am I connected to?"*. The server answers that a login is required, and claude.ai opens the Accounted sign-in (BankID or e-mail + 2FA), followed by the consent page. It shows the company that is currently active in the app (switch company in the app first if you have several) with every permission pre-selected; expand **Behörigheter** and choose **Endast läs** for a read-only connection. Approve, then **ask the question again**: the question that was waiting when the sign-in opened is not retried on its own. A "connected" status with an unanswered first question means "sign in, then ask again", not a broken connection.
|
|
4. **Done.** From here every question runs against that company; writes stage at **/pending** until you confirm.
|
|
|
|
Signed in, but Claude still says it cannot reach the server? Ask again in the same chat first. If that does not help, open Settings → Connectors, remove the connector, and add it again with authentication set to "Required when the server asks".
|
|
|
|
#### Adding it by hand instead
|
|
|
|
In **claude.ai** (Settings → Connectors) or **Claude Desktop** (Settings → Connectors → Add custom connector), choose **Add custom connector** and paste:
|
|
|
|
\`\`\`
|
|
https://app.accounted.se/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted&client=claude-connector
|
|
\`\`\`
|
|
|
|
Keep \`tool_namespace=accounted\`: it selects the tool names this guide uses. \`client=claude-connector\` is telemetry-only. If the dialog asks about authentication, choose **"Required when the server asks"**, not the auto-detected "None".
|
|
|
|
## Path B: Claude Code (plugin)
|
|
|
|
Best in the terminal. The plugin installs the connection *and* seven workflow commands that follow the Swedish bookkeeping rhythm.
|
|
|
|
\`\`\`text
|
|
/plugin marketplace add erp-mafia/accounted
|
|
/plugin install accounted@accounted
|
|
\`\`\`
|
|
|
|
Then run \`/mcp\` and sign in with Accounted (the same OAuth consent screen as Path A). Start with \`/accounted:start\`.
|
|
|
|
| Command | What it does |
|
|
|---|---|
|
|
| \`/accounted:start\` | Connect, orient, and surface what needs attention |
|
|
| \`/accounted:bookkeep\` | Clear unbooked bank transactions and receipts |
|
|
| \`/accounted:check\` | Read-only health check with a prioritized fix list |
|
|
| \`/accounted:month-close\` | Close the month against the product's checklist |
|
|
| \`/accounted:vat\` | Prepare and reconcile the momsdeklaration |
|
|
| \`/accounted:payroll\` | Monthly salary run and AGI underlag |
|
|
| \`/accounted:year-end\` | Bokslut, readiness-gated |
|
|
|
|
Prefer plain MCP without the workflow commands? \`claude mcp add\` wires the same connection into Claude Code:
|
|
|
|
\`\`\`bash
|
|
claude mcp add --transport http accounted \\
|
|
"https://app.accounted.se/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted&client=claude-code"
|
|
\`\`\`
|
|
|
|
**Cursor** has no plugin format and does not read \`claude mcp add\`. Add the server to \`~/.cursor/mcp.json\` (global) or \`.cursor/mcp.json\` (per project) instead:
|
|
|
|
\`\`\`json
|
|
{
|
|
"mcpServers": {
|
|
"accounted": {
|
|
"url": "https://app.accounted.se/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted&client=cursor"
|
|
}
|
|
}
|
|
}
|
|
\`\`\`
|
|
|
|
## Path C: \`npx accounted-mcp\` with an API key (stdio bridge)
|
|
|
|
Best for Claude Desktop on a machine where you'd rather use a long-lived API key than the OAuth flow, or for scripting.
|
|
|
|
1. Mint an API key in the Accounted dashboard under **Settings → API & MCP** (\`/settings/api\`). Use a \`gnubok_sk_test_*\` key against the sandbox while you evaluate; switch to \`gnubok_sk_live_*\` for real data.
|
|
2. Add the stdio bridge to your \`claude_desktop_config.json\`:
|
|
\`\`\`json
|
|
{
|
|
"mcpServers": {
|
|
"accounted": {
|
|
"command": "npx",
|
|
"args": ["-y", "accounted-mcp"],
|
|
"env": {
|
|
"ACCOUNTED_API_KEY": "gnubok_sk_test_...",
|
|
"ACCOUNTED_CLIENT": "claude-desktop"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
\`\`\`
|
|
Running Accounted yourself? Point the bridge at your own host with \`ACCOUNTED_URL\`.
|
|
3. Restart Claude Desktop. The bridge proxies stdio JSON-RPC to the hosted MCP endpoint over HTTPS; the key carries the scopes you granted it at mint time.
|
|
|
|
The key's scopes gate exactly which tools are callable: a key without write scopes can read reports and ledgers but cannot stage a booking.
|
|
|
|
The API-key value still begins with \`gnubok_sk_\`. That is a stable credential
|
|
format, not the MCP integration name. Existing \`gnubok-mcp\` configurations
|
|
continue to work without changes.
|
|
|
|
## Try these prompts
|
|
|
|
All three run against the deterministic sandbox seed (use a \`gnubok_sk_test_*\` key, or make the sandbox company the active company in the app before you sign in from Claude). They exercise the read path end-to-end without booking anything.
|
|
|
|
1. **"Show my uncategorized bank transactions and suggest categories."**
|
|
Claude calls \`accounted_list_uncategorized_transactions\` then \`accounted_suggest_categories\` and walks you through the proposals. Approving one stages an \`accounted_categorize_transaction\` pending operation: nothing is booked until you confirm.
|
|
2. **"Which invoices are overdue?"**
|
|
Claude calls \`accounted_get_ar_ledger\` (kundreskontra) and lists outstanding customer invoices with aging.
|
|
3. **"Compute my VAT report for this quarter and tell me if I can close it."**
|
|
Claude calls \`accounted_get_vat_report\` for the momsdeklaration rutor, then \`accounted_vat_close_check\` to scan for blockers (uncategorised rows, unapproved supplier invoices, missing receipts on expenses ≥ 4 000 kr: the tool's high-value heuristic; BFL requires underlag for every affärshändelse regardless of amount) and reports \`ready_to_close\`.
|
|
|
|
## 10-minute reviewer test
|
|
|
|
A quick end-to-end pass to confirm the connection works before you trust it with real data. Run the steps in order; each lists what you do and what you should see.
|
|
|
|
1. **Connect.** Use Path A (choose **Endast läs** on the consent page), Path B, or Path C with a \`gnubok_sk_test_*\` key. → Claude lists the Accounted tools (titles like *List Uncategorized Transactions*, *VAT Declaration (Momsdeklaration)*).
|
|
2. **Confirm the company.** Ask *"Which company am I connected to?"* → Claude names the sandbox company (e.g. **Sandlådan Konsult**).
|
|
3. **Run prompt 1** (*uncategorized + suggest categories*). → A list of uncategorised rows plus category suggestions; no booking happens.
|
|
4. **Run prompt 2** (*overdue invoices*). → At least one overdue customer invoice with aging.
|
|
5. **Run prompt 3** (*VAT report + can I close*). → Momsdeklaration rutor returned; \`accounted_vat_close_check\` reports a **non-empty blocker list** (uncategorised transactions, an unapproved leverantörsfaktura, and a high-value business expense without a receipt).
|
|
6. **Stage a write.** Ask Claude to categorise one transaction. → Claude stages a pending operation and asks you to confirm: the booking does **not** post until you approve in chat or at **/pending**.
|
|
|
|
If every step matches, the connector is wired correctly and the approval model is enforced.
|
|
|
|
## Support
|
|
|
|
Stuck connecting, or seeing an unexpected blocker? Use the in-app support form at **/help**: it routes straight to the product team with your company context attached. Include the client (claude.ai / Desktop / Code), the path you used (A, B, or C), and the tool name from any error message.
|
|
`
|