feat(mcp): tool titles + Connect-with-Claude docs for Connectors Directory readiness (P0-4) (#682)

* feat(mcp): tool titles + Connect-with-Claude docs for Connectors Directory readiness

Add a top-level Tool.title (MCP spec 2025-06-18) to all 92 MCP tools and
flow it through the tools/list serializer for the Claude Connectors
Directory. Trim the ~10 longest tool descriptions toward 180-200 chars
(semantics + agent hints preserved) to partly offset the added bytes, and
raise the payload-size bench ceiling 32K → 36K with headroom reserved for
upcoming Skatteverket tools.

Ship a "Connect with Claude" docs page (OAuth 2.1 connector + npx
gnubok-mcp stdio bridge, sample sandbox prompts, 10-minute reviewer test,
support pointer), wire it into the docs nav, and add a README to the
packages/gnubok-mcp npm bridge. Bump one categorized sandbox business
expense above 4 000 kr so the VAT close-check demo surfaces a high-value
receipt-less expense.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mcp): restore six spaces lost in the description-trimming pass

Review finding (Greptile P2): the trim deleted single spaces in six tool
descriptions ("Stages foruser", "länkatill", "kundfordran(1510)", …),
producing malformed text in tools/list. All six were pure space deletions
with no token gain — restored verbatim. Swept all 92 descriptions for
residual merge artifacts; the two remaining matches are pre-existing
function-call notation from main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(compliance): correct BFL citation in seed + mark 4000 kr as app heuristic

Swedish compliance review findings:
- The seed comment cited BFL 5 kap 6§ as requiring verifikat on high-value
  affärshändelser; 6§ governs verifikationens innehåll. Rephrased: BFL 5 kap
  6-7§ require every affärshändelse to be documented — the 4 000 kr cut-off
  is the tool's own heuristic, not a statutory threshold.
- The connect-claude docs read as though 4 000 kr were a legal floor
  (conflatable with the förenklad faktura ceiling, ML 17 kap 26-28§).
  Clarified that BFL requires underlag regardless of amount.

Dismissed as by-design: the gnubok_sk_test_... README placeholders
(standard practice; the prefix schema is already public in llms.txt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-06-06 11:47:21 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 0bc81d4c88
commit a894af11d0
10 changed files with 289 additions and 15 deletions
+6 -1
View File
@@ -504,7 +504,12 @@ export async function POST(request: Request) {
company_id: companyId,
date: toDateStr(fiveDaysAgo),
description: 'SJ BILJETT',
amount: -2500,
// > 4 000 kr categorized business expense with no attached underlag,
// so gnubok_vat_close_check surfaces a non-empty blocker list.
// (BFL 5 kap 6–7§ require every affärshändelse to be documented with
// underlag; the 4 000 kr cut-off is the tool's own high-value
// heuristic, not a statutory threshold.)
amount: -4500,
category: 'expense_travel',
is_business: true,
merchant_name: 'SJ',
+13
View File
@@ -0,0 +1,13 @@
import { NextResponse } from 'next/server'
import { CONNECT_CLAUDE_MD } from '@/lib/docs/content/connect-claude'
import { withPublicSecurityHeaders } from '@/lib/api/v1/security-headers'
export async function GET() {
return new NextResponse(CONNECT_CLAUDE_MD, {
status: 200,
headers: withPublicSecurityHeaders({
'Content-Type': 'text/markdown; charset=utf-8',
'Cache-Control': 'public, max-age=300, s-maxage=300',
}),
})
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from 'next'
import { DocsLayout } from '@/components/docs/DocsLayout'
import { DocsMarkdown } from '@/lib/docs/markdown'
import { CONNECT_CLAUDE_MD } from '@/lib/docs/content/connect-claude'
export const metadata: Metadata = {
title: 'Connect with Claude · accounted API',
description:
'Connect Accounted to Claude (claude.ai, Claude Desktop, Claude Code) via the MCP server — OAuth 2.1 connector or the npx gnubok-mcp stdio bridge.',
}
export default function DocsApiConnectClaudePage() {
return (
<DocsLayout currentPath="/docs/api/connect-claude">
<DocsMarkdown source={CONNECT_CLAUDE_MD} />
</DocsLayout>
)
}
+1 -1
View File
@@ -21,7 +21,7 @@ For internal Anthropic API usage (today only `extensions/general/invoice-inbox/l
## Payload-size watchdog
`payload-size.bench.test.ts` enforces a `tools/list` JSON payload ceiling (currently 25,000 tokens). If the test fires, the right answer is rarely "raise the ceiling" — instead, trim descriptions or leverage `gnubok_search_tools` (already deployed; tool definitions can defer to it for discovery rather than enumerating in `tools/list`).
`payload-size.bench.test.ts` enforces a `tools/list` JSON payload ceiling (currently 36,000 tokens — bumped from 32,000 when top-level `Tool.title` landed on all tools for Claude Connectors Directory readiness). If the test fires, the right answer is rarely "raise the ceiling" — instead, trim descriptions or leverage `gnubok_search_tools` (already deployed; tool definitions can defer to it for discovery rather than enumerating in `tools/list`).
## Where things live
@@ -5,6 +5,7 @@ describe('tools/list payload size guard', () => {
it('keeps the projected tools/list payload under the context-budget ceiling', () => {
const projection = tools.map((t) => ({
name: t.name,
...(t.title ? { title: t.title } : {}),
description: t.description,
inputSchema: t.inputSchema,
...(t.outputSchema ? { outputSchema: t.outputSchema } : {}),
@@ -13,7 +14,7 @@ describe('tools/list payload size guard', () => {
}))
const payload = JSON.stringify({ tools: projection })
const approxTokens = Math.round(payload.length / 4)
// Ceiling progression: 20K → 25K → 30K → 31K → 31.5K.
// Ceiling progression: 20K → 25K → 30K → 31K → 31.5K → 32K → 36K.
// * 20K → 25K when item 8 of the agent-native API plan landed
// (additionalProperties: false on all inputSchemas + period_status in the
// staged operation envelope).
@@ -38,9 +39,13 @@ describe('tools/list payload size guard', () => {
// faktura paid against an already-posted verifikat (no new bokföring),
// which is exactly the fix for invoices imported from Fortnox as open
// payables while their payment already exists in the SIE-imported GL.
// * 32K → 36K when top-level Tool.title (MCP spec 2025-06-18) landed on all
// 92 tools for Connectors Directory readiness; the ~10 longest descriptions
// were trimmed toward 180–200 chars to partly offset. Headroom reserved for
// the upcoming Skatteverket tools.
// Long-term answer to growth is leaning harder on gnubok_search_tools — if this
// fires again, prefer trimming descriptions or making a tool opt-in via search
// before bumping further.
expect(approxTokens).toBeLessThan(32_000)
expect(approxTokens).toBeLessThan(36_000)
})
})
+107 -10
View File
@@ -121,6 +121,10 @@ interface McpToolAnnotations {
interface McpTool {
name: string
// Top-level Tool.title per MCP spec 2025-06-18 (human-facing label for
// directory listings; distinct from annotations.title). Short Title Case
// noun phrase. Flows out via the tools/list serializer below.
title?: string
description: string
inputSchema: Record<string, unknown>
outputSchema?: Record<string, unknown>
@@ -1399,6 +1403,7 @@ function previousPeriodArgs(
export const tools: McpTool[] = [
{
name: 'gnubok_search_tools',
title: 'Search MCP Tools',
description: 'Search Accounted MCP tools by keyword and return their schemas at a chosen detail level. Call this first when looking for a capability — avoids loading every tool schema upfront.',
inputSchema: {
type: 'object',
@@ -1517,6 +1522,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_skills',
title: 'List Domain Skills',
description: 'List available domain-knowledge skills filtered to this company (entity type, VAT, payroll). Workflow guides + loaded specialty atoms. Pass include_all=true to see hidden skills. Call gnubok_load_skill(slug) for any body.',
inputSchema: {
type: 'object',
@@ -1641,6 +1647,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_load_skill',
title: 'Load Domain Skill',
description: 'Load a skill body by slug. Workflow slugs are flat (e.g. "month-end-close"); atom slugs match registry ids (e.g. "vertical/konsult-it", "modifier/holding-ab"). Call gnubok_list_skills to find slugs.',
inputSchema: {
type: 'object',
@@ -1704,7 +1711,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_remember_fact',
description: 'Capture a durable fact, preference, pattern, or correction about the company. Use mid-conversation when the user says something the agent should remember next time. Writes immediately — does not stage. Use sparingly for foundational signal, not for one-off context.',
title: 'Remember Company Fact',
description: 'Capture a durable fact, preference, or correction about the company. Use mid-conversation when the user says something to remember next time. Writes immediately — does not stage. Use sparingly.',
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -1818,6 +1826,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_forget_fact',
title: 'Forget Company Fact',
description: 'Deactivate a memory entry by id. Use when the user explicitly asks to forget something or supersedes it. The row is kept for audit (is_active=false) but no longer surfaces in prompts.',
inputSchema: {
type: 'object',
@@ -1860,6 +1869,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_feedback',
title: 'Send Agent Feedback',
description: 'Report agent-side feedback: missing tool, wrong description, skill gap, or a positive signal. Goes to event_log for product-team triage. Rate-limited 1/min/key.',
inputSchema: {
type: 'object',
@@ -1958,6 +1968,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_agent_briefing',
title: 'Get Agent Briefing',
description: 'Bootstrap this company\'s specialized accountant context in one call: profile_summary, the atoms loaded for the company (metadata only — call gnubok_load_skill for bodies), and the top-30 active memories. Call once at session start.',
inputSchema: {
type: 'object',
@@ -2088,7 +2099,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_transactions',
description: 'Stage one or more transactions for the user to approve. Each item creates a separate pending operation; commit each via gnubok_approve_pending_operation when the user authorises. Useful for ingesting rows from external sources (Airtable, CSVs, etc.). Max 10 per call.',
title: 'Create Bank Transactions',
description: 'Stage one or more transactions for the user to approve. Each creates a separate pending operation; commit each via gnubok_approve_pending_operation. Use for ingesting external rows (Airtable, CSV). Max 10.',
outputSchema: {
type: 'object',
additionalProperties: false,
@@ -2200,6 +2212,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_uncategorized_transactions',
title: 'List Uncategorized Transactions',
description: 'List bank transactions with no journal entry yet, newest first. Paginated.',
inputSchema: {
type: 'object',
@@ -2270,6 +2283,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_transactions_without_documents',
title: 'List Transactions Missing Receipts',
description: 'List BANK TRANSACTIONS booked without an attached underlag. For imported/manual verifikat (no bank tx row) call gnubok_list_verifikat_without_documents — this tool only covers bank-driven entries.',
inputSchema: {
type: 'object',
@@ -2349,6 +2363,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_verifikat_without_documents',
title: 'List Verifikat Missing Documents',
description: 'List POSTED journal entries (verifikat) that have no document_attachments row. Covers SIE-imported, manual and salary vouchers that the transactions-based tool misses. Newest first, paginated.',
inputSchema: {
type: 'object',
@@ -2446,7 +2461,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_categorize_transaction',
description: 'Categorize a bank transaction. Stages the journal entry; commit via gnubok_approve_pending_operation when the user authorises. If the row has an attached underlag, the tool reads its extracted_data and rejects vat_treatment="reverse_charge" when the seller already charged VAT.',
title: 'Categorize Bank Transaction',
description: 'Categorize a bank transaction. Stages the journal entry; commit via gnubok_approve_pending_operation. If an underlag is attached it rejects vat_treatment="reverse_charge" when the seller already charged VAT.',
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -2529,6 +2545,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_receipt_matcher',
title: 'Receipt Matcher Widget',
description: 'Open an interactive widget for drag-and-drop receipt-to-transaction matching. Renders inline in compatible clients.',
inputSchema: {
type: 'object',
@@ -2581,6 +2598,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_customers',
title: 'List Customers',
description: 'List all customers for the active company. Use to look up customer_id for invoice creation.',
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
outputSchema: {
@@ -2613,6 +2631,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_customer',
title: 'Create Customer',
description: 'Stage a new customer. Stages for user approval — NOT created until approved in the web app. EU VAT numbers trigger VIES validation.',
outputSchema: STAGED_OPERATION_SCHEMA,
inputSchema: {
@@ -2693,6 +2712,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_invoices',
title: 'List Customer Invoices',
description: 'List invoices for the active company, newest first. Optional status filter.',
inputSchema: {
type: 'object',
@@ -2754,6 +2774,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_invoice',
title: 'Create Customer Invoice',
description: 'Stage a new invoice. Validates inputs, calculates VAT preview. Stages for user approval — invoice number assigned at approval.',
outputSchema: STAGED_OPERATION_SCHEMA,
inputSchema: {
@@ -2898,6 +2919,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_trial_balance',
title: 'Trial Balance (Råbalans)',
description: 'Trial balance (huvudbok) for a fiscal period — all account balances with debit/credit totals. Defaults to most recent period.',
inputSchema: {
type: 'object',
@@ -3016,6 +3038,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_vat_report',
title: 'VAT Declaration (Momsdeklaration)',
description: 'VAT declaration (momsdeklaration, SKV 4700) for a period. Returns all rutor; ruta49 = VAT to pay (positive) or refund (negative). Pass render_ui=true to also open the review widget (claude.ai / Desktop).',
outputSchema: VAT_REPORT_OUTPUT_SCHEMA,
inputSchema: {
@@ -3053,6 +3076,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_vat_review_widget',
title: 'VAT Review Widget',
description: 'Open the interactive VAT review widget for a period. Equivalent to gnubok_get_vat_report(render_ui=true); kept as an alias for clients pinned to this tool name.',
inputSchema: {
type: 'object',
@@ -3079,7 +3103,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_vat_close_check',
description: "Answer 'can I close VAT?' in one call. Returns SKV 4700 rutor + blocker scan (uncategorized, unapproved supplier invoices, reconciliation diff, missing receipts ≥ 4000 kr, reverse-charge mirroring) + period sanity ratios + Skatteverket deadline + ready_to_close.",
title: 'VAT Close Check (Momsdeklaration)',
description: "Answer 'can I close VAT?' in one call. Returns SKV 4700 rutor + blocker scan (uncategorized, unapproved supplier invoices, reconciliation diff, missing receipts ≥ 4000 kr, reverse-charge) + ready_to_close.",
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -3129,6 +3154,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_kpi_report',
title: 'Business KPI Report',
description: 'Business KPIs for a fiscal period: gross margin, net result, cash position, receivables, expense ratio, payment days, VAT liability, monthly trend.',
inputSchema: {
type: 'object',
@@ -3231,6 +3257,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_income_statement',
title: 'Income Statement (Resultaträkning)',
description: 'Income statement (resultaträkning) for a fiscal period: revenue, expenses, net result by account category.',
inputSchema: {
type: 'object',
@@ -3287,6 +3314,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_mark_invoice_as_paid',
title: 'Mark Invoice as Paid',
description: 'Mark an invoice as paid and create the payment journal entry. Stages for approval. Status must be sent or overdue.',
inputSchema: {
type: 'object',
@@ -3344,6 +3372,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_send_invoice',
title: 'Send Invoice by Email',
description: 'Send invoice via email with PDF attachment. Stages for approval. Requires customer email + email service configured.',
inputSchema: {
type: 'object',
@@ -3403,6 +3432,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_mark_invoice_as_sent',
title: 'Mark Invoice as Sent',
description: 'Mark a draft invoice as sent without sending email (when delivered manually). Stages for approval. Status must be draft.',
inputSchema: {
type: 'object',
@@ -3456,6 +3486,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_suppliers',
title: 'List Suppliers (Leverantörer)',
description: 'List all suppliers (leverantörer) with contact and payment details, sorted by name.',
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
outputSchema: {
@@ -3488,6 +3519,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_supplier',
title: 'Create Supplier (Leverantör)',
description: 'Stage a new supplier (leverantör). Stages for user approval — NOT created until approved in the web app. Use to add a vendor before booking a supplier invoice or matching expenses.',
outputSchema: STAGED_OPERATION_SCHEMA,
inputSchema: {
@@ -3623,6 +3655,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_supplier_invoices',
title: 'List Supplier Invoices',
description: 'List supplier invoices (leverantörsfakturor), sorted by due date. Optional status filter; "to_pay" combines approved+overdue.',
inputSchema: {
type: 'object',
@@ -3680,6 +3713,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_counterparty_templates',
title: 'List Counterparty Templates',
description: 'List active counterparty categorization templates — learned patterns from prior categorizations used for auto-matching new transactions.',
inputSchema: {
type: 'object',
@@ -3728,6 +3762,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_suggest_categories',
title: 'Suggest Transaction Categories',
description: 'Suggest categories for uncategorized transactions using mapping rules, pattern matching, history, and counterparty templates. Up to 20 transactions per call.',
inputSchema: {
type: 'object',
@@ -3830,6 +3865,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_accounts',
title: 'List Chart of Accounts (Kontoplan)',
description: 'List chart of accounts (kontoplan). account_class: 1=assets, 2=liabilities, 3=revenue, 4–7=expenses, 8=financial.',
inputSchema: {
type: 'object',
@@ -3879,6 +3915,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_balance_sheet',
title: 'Balance Sheet (Balansräkning)',
description: 'Balance sheet (balansräkning) for a fiscal period: assets, equity, and liabilities sections with totals + balance check.',
inputSchema: {
type: 'object',
@@ -3931,6 +3968,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_general_ledger',
title: 'General Ledger (Huvudbok)',
description: 'General ledger (huvudbok) for a fiscal period: per-account opening balance, entries, closing balance. Optional account range filter. For ad-hoc cross-account, amount, or free-text line queries use gnubok_query_journal.',
inputSchema: {
type: 'object',
@@ -3973,6 +4011,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_query_journal',
title: 'Query Journal Lines',
description: "Flexible journal-line query — replaces chained ledger calls for ad-hoc questions. Filters: accounts, date range, amount range, voucher series/number, source type, status, project, cost center, free-text. Returns lines with parent voucher metadata + totals.",
inputSchema: {
type: 'object',
@@ -4297,6 +4336,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_ar_ledger',
title: 'AR Ledger (Kundreskontra)',
description: 'Accounts receivable ledger (kundreskontra): outstanding customer invoices with aging.',
inputSchema: {
type: 'object',
@@ -4320,6 +4360,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_supplier_ledger',
title: 'AP Ledger (Leverantörsreskontra)',
description: 'Accounts payable ledger (leverantörsreskontra): outstanding supplier invoices with aging.',
inputSchema: {
type: 'object',
@@ -4345,6 +4386,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_match_transaction_to_invoice',
title: 'Match Transaction to Invoice',
description: 'Match a bank transaction (income, amount>0) to a customer invoice. Confirm tx date/amount and invoice number/customer match before staging — preview mirrors what you pass. Supports partial payments and auto-storno of prior categorization.',
inputSchema: {
type: 'object',
@@ -4421,7 +4463,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_match_batch_allocate',
description: 'Allocate 1 bank tx across N customer OR N supplier invoices (samlingsbetalning, BFL 5 kap 6§). Use when one receipt covers many invoices, or one transfer pays many bills. Customer kind requires income tx; supplier kind requires expense. Never mix kinds. Stages.',
title: 'Batch-Allocate Payment',
description: 'Allocate 1 bank tx across N customer OR N supplier invoices (samlingsbetalning, BFL 5 kap 6§). Use when one receipt covers many invoices or one transfer pays many bills. Customer needs income, supplier expense. Stages.',
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -4609,6 +4652,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_link_transaction_to_journal_entry',
title: 'Link Transaction to Verifikat',
description: 'Link 1 bank tx to an already-posted verifikat (no new bokföring). Use when the user booked the affärshändelse manually. Pass invoice_id to also settle a kundfaktura. Stages.',
inputSchema: {
type: 'object',
@@ -4762,7 +4806,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_bulk_book_transactions',
description: 'Bulk-book N bank txs on the same date into 1 samlingsverifikat (BFL 5 kap 6§). Two paths: link N txs to an existing posted verifikat, or create a new verifikat from caller-supplied lines. All txs must share date + direction. Docs on the txs inherit. Stages.',
title: 'Bulk-Book Transactions',
description: 'Bulk-book N bank txs on the same date into 1 samlingsverifikat (BFL 5 kap 6§). Either link N txs to an existing posted verifikat, or create a new verifikat from caller lines. All txs share date + direction. Stages.',
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -4948,6 +4993,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_find_voucher_candidates_for_invoice',
title: 'Find Voucher Candidates (Invoice)',
description: 'List posted verifikat that credit kundfordran (1510) and could be the payment for this invoice. Use before gnubok_link_invoice_to_voucher when the user wants to mark a faktura paid against an existing verifikation (no new bokföring).',
inputSchema: {
type: 'object',
@@ -5013,6 +5059,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_link_invoice_to_voucher',
title: 'Link Invoice to Voucher',
description: 'Markera en faktura som betald genom att länka till en befintlig verifikation som redan krediterar kundfordran (1510). Ingen ny verifikation skapas. Hitta kandidater med gnubok_find_voucher_candidates_for_invoice först.',
inputSchema: {
type: 'object',
@@ -5099,6 +5146,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_find_voucher_candidates_for_supplier_invoice',
title: 'Find Voucher Candidates (Supplier Invoice)',
description: 'List posted verifikat that debit leverantörsskuld (2440) and could be the payment for this supplier invoice. Use before gnubok_link_supplier_invoice_to_voucher when marking a leverantörsfaktura paid against an existing verifikation (no new bokföring).',
inputSchema: {
type: 'object',
@@ -5164,6 +5212,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_link_supplier_invoice_to_voucher',
title: 'Link Supplier Invoice to Voucher',
description: 'Markera en leverantörsfaktura som betald genom att länka till en befintlig verifikation som redan debiterar leverantörsskuld (2440). Ingen ny verifikation skapas. Hitta kandidater med gnubok_find_voucher_candidates_for_supplier_invoice först.',
inputSchema: {
type: 'object',
@@ -5250,7 +5299,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_auto_match_period',
description: "Bulk reconciliation: scan unmatched income transactions in a date range and propose invoice matches with confidence + reasoning. dry_run=true (default) previews without staging; dry_run=false stages every match above confidence_threshold as a pending operation.",
title: 'Auto-Match Period Income',
description: "Bulk reconciliation: scan unmatched income in a date range and propose invoice matches with confidence + reasoning. dry_run=true (default) previews; dry_run=false stages matches above confidence_threshold.",
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -5440,6 +5490,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_fiscal_periods',
title: 'List Fiscal Periods',
description: 'List all fiscal periods (räkenskapsperioder) with status: active (open), locked (no new entries), or closed (year-end completed).',
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
outputSchema: {
@@ -5483,6 +5534,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_reconciliation_status',
title: 'Bank Reconciliation Status',
description: 'Bank reconciliation status: matched/unmatched counts, match rate, bank vs ledger balance, difference. Optional date range.',
inputSchema: {
type: 'object',
@@ -5510,6 +5562,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_upload_document',
title: 'Upload Document to Inbox',
description: 'Upload a PDF/JPEG/PNG/HEIC/WebP (max 20 MB) to the inbox. Runs deterministic field extraction on text-based PDFs.',
inputSchema: {
type: 'object',
@@ -5622,6 +5675,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_inbox_items',
title: 'List Inbox Items',
description: 'List document inbox items. Each has a `processed` flag covering all terminal links (transaction match, supplier invoice, or journal entry), so a booked receipt counts as done. unprocessed_only=true returns only docs still needing handling.',
inputSchema: {
type: 'object',
@@ -5720,6 +5774,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_inbox_item',
title: 'Get Inbox Item',
description: 'Get a single inbox item with complete extracted data, supplier match, email metadata, and timestamps.',
inputSchema: {
type: 'object',
@@ -5755,6 +5810,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_supplier_invoice_from_inbox',
title: 'Create Supplier Invoice from Inbox',
description: "Atomic: turn an OCR'd inbox item into a staged supplier invoice. Resolves supplier (matched or via org_number/name), assembles line items from extracted_data, applies VAT + FX, attaches the source document. Stages for human review; honors dry_run.",
inputSchema: {
type: 'object',
@@ -5946,6 +6002,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_unmatched_documents',
title: 'List Unmatched Documents',
description: 'List inbox documents not yet attached to any bank transaction or supplier invoice. Returns vendor/amount/currency/date hints. The amount is in the invoice currency — FX-normalise before comparing to transactions.amount.',
inputSchema: {
type: 'object',
@@ -6100,6 +6157,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_get_document_content',
title: 'Get Document Content',
description: 'Get a 5-minute signed download URL for a document so the agent can read its contents (e.g. with vision). Use after gnubok_list_unmatched_documents to inspect a specific PDF before deciding which transaction it matches.',
inputSchema: {
type: 'object',
@@ -6166,6 +6224,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_attach_document_to_transaction',
title: 'Attach Document to Transaction',
description: 'Stage attaching a document to a bank transaction. Verify tx (date, amount, counterparty) and document (filename, vendor, amount) match first — the preview shown to the human reviewer mirrors what you pass here. Stages for approval.',
inputSchema: {
type: 'object',
@@ -6295,6 +6354,7 @@ export const tools: McpTool[] = [
// ── Payroll (Lönehantering) ──────────────────────────────────
{
name: 'gnubok_list_employees',
title: 'List Employees',
description: 'List employees for the active company. Personnummer returned masked (YYYYMMDD-XXXX).',
inputSchema: {
type: 'object',
@@ -6328,6 +6388,7 @@ export const tools: McpTool[] = [
},
{
name: 'gnubok_get_salary_run',
title: 'Get Salary Run',
description: 'Get salary run with status, totals, per-employee breakdown (gross, tax, net, avgifter, vacation accrual) and step-by-step calculation breakdown.',
inputSchema: {
type: 'object',
@@ -6357,6 +6418,7 @@ export const tools: McpTool[] = [
},
{
name: 'gnubok_get_salary_journal',
title: 'Salary Journal (Lönejournal)',
description: 'Salary journal (lönejournal) for a year: per-employee per-month rows + yearly totals.',
inputSchema: {
type: 'object',
@@ -6375,6 +6437,7 @@ export const tools: McpTool[] = [
},
{
name: 'gnubok_create_salary_run',
title: 'Create Salary Run',
description: 'Stage creation of a draft salary run for a period + base lines for all active employees. Commit via gnubok_approve_pending_operation; then run gnubok_calculate_salary_run. Final booking happens in the web UI.',
inputSchema: {
type: 'object',
@@ -6430,6 +6493,7 @@ export const tools: McpTool[] = [
},
{
name: 'gnubok_calculate_salary_run',
title: 'Calculate Salary Run',
description: 'Calculate a draft salary run: tax, avgifter, vacation accrual, totals. Run must be in draft status.',
inputSchema: {
type: 'object',
@@ -6475,6 +6539,7 @@ export const tools: McpTool[] = [
},
{
name: 'gnubok_generate_agi',
title: 'Generate AGI Declaration',
description: 'Stage AGI XML generation (Arbetsgivardeklaration) for a salary run. High-risk: produces statutory Skatteverket underlag (BFL 7-year retention). Commit via gnubok_approve_pending_operation.',
inputSchema: {
type: 'object',
@@ -6523,6 +6588,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_close_period',
title: 'Close Fiscal Period',
description: 'Stage period close (irreversible per BFL). Requires period locked + year-end closing entry posted. High-risk — always staged, never auto-committed.',
inputSchema: {
type: 'object',
@@ -6578,6 +6644,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_lock_period',
title: 'Lock Fiscal Period',
description: 'Stage period lock — blocks new entries. Requires zero unbooked business transactions. High-risk, always staged.',
inputSchema: {
type: 'object',
@@ -6645,6 +6712,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_uncategorize_transaction',
title: 'Uncategorize Transaction',
description: 'Stage uncategorize: reverses linked journal entry via storno (never deletes) and clears the category. Stages for approval.',
inputSchema: {
type: 'object',
@@ -6710,6 +6778,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_export_sie',
title: 'Export SIE File',
description: 'Generate SIE-4 file for a fiscal period (standard Swedish bookkeeping interchange format). Returns SIE text content.',
inputSchema: {
type: 'object',
@@ -6767,7 +6836,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_audit_package',
description: "Single-call audit package for a fiscal period: SIE-4 + reports (trial balance, income statement, balance sheet, general ledger, journal register, VAT) + receipts + audit log + voucher gaps, zipped. Returns a 1-hour signed download URL. Long-running on large datasets.",
title: 'Generate Audit Package',
description: "Single-call audit package for a fiscal period: SIE-4 + reports (trial balance, income statement, balance sheet, general ledger, journal, VAT) + receipts + audit log + voucher gaps, zipped. 1-hour signed URL.",
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -6907,7 +6977,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_year_end_readiness',
description: "Pre-flight before irreversible gnubok_run_year_end. Returns ready (bool) + ordered blockers (drafts, voucher gaps, sequence mismatches, unbalanced trial balance, FX revaluation needed) + warnings + optional preview of the closing entry. Use this before staging year-end.",
title: 'Year-End Readiness Check',
description: "Pre-flight before irreversible gnubok_run_year_end. Returns ready (bool) + ordered blockers (drafts, voucher gaps, sequence mismatches, unbalanced TB, FX revaluation) + optional closing-entry preview.",
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -7017,6 +7088,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_run_year_end',
title: 'Run Year-End Closing (Bokslut)',
description: 'Stage year-end closing: zero result accounts (class 3–8) into 2099, lock period, create next period, seed opening balances. High-risk, always staged.',
inputSchema: {
type: 'object',
@@ -7061,6 +7133,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_set_opening_balances',
title: 'Set Opening Balances (Ingående Balans)',
description: 'Stage opening-balance entry: copy class 1–2 closing balances from a closed period into the next period.',
inputSchema: {
type: 'object',
@@ -7110,6 +7183,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_run_currency_revaluation',
title: 'Run Currency Revaluation',
description: 'Stage currency revaluation: revalue open FX receivables/payables to closing-date rate (posts 3960/7960). One per period max.',
inputSchema: {
type: 'object',
@@ -7143,6 +7217,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_list_voucher_gaps',
title: 'List Voucher Gaps',
description: 'List voucher number gaps in a fiscal period (BFNAR 2013:2 audit requirement). Each gap shows whether it has an explanation.',
inputSchema: {
type: 'object',
@@ -7215,6 +7290,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_explain_voucher_gap',
title: 'Explain Voucher Gap',
description: 'Stage explanation for a voucher gap (BFNAR 2013:2 compliance — every gap needs a documented reason).',
inputSchema: {
type: 'object',
@@ -7261,6 +7337,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_approve_supplier_invoice',
title: 'Approve Supplier Invoice',
description: 'Stage approval of a registered supplier invoice (registered → approved). High-risk, always staged.',
inputSchema: {
type: 'object',
@@ -7303,6 +7380,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_credit_supplier_invoice',
title: 'Credit Supplier Invoice (Kreditfaktura)',
description: 'Stage credit-note (kreditfaktura) for a supplier invoice: mirror invoice with negative effect + reverses registration JE (accrual).',
inputSchema: {
type: 'object',
@@ -7344,6 +7422,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_convert_invoice',
title: 'Convert Proforma to Invoice',
description: 'Stage conversion of a proforma invoice to a real invoice. Allocates F-series number, copies items, marks proforma cancelled.',
inputSchema: {
type: 'object',
@@ -7386,6 +7465,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_unlock_period',
title: 'Unlock Fiscal Period',
description: 'Stage period unlock — clears locked_at so entries can be posted again. Cannot unlock a closed period. High-risk, always staged.',
inputSchema: {
type: 'object',
@@ -7434,6 +7514,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_credit_invoice',
title: 'Credit Customer Invoice (Kreditfaktura)',
description: 'Stage credit note (kreditfaktura) for a customer invoice: KR- prefixed mirror invoice + reverses original JE (accrual). Original must be sent/paid/overdue and not already credited.',
inputSchema: {
type: 'object',
@@ -7487,6 +7568,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_import_sie',
title: 'Import SIE File',
description: 'Stage SIE-file import (types 1–4, CP437/UTF-8/Latin-1). On commit creates fiscal period, opening balances, and journal entries. High-risk, always staged.',
inputSchema: {
type: 'object',
@@ -7616,6 +7698,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_undo_sie_import',
title: 'Undo SIE Import',
description: 'Stage undo of a completed SIE import: hard-deletes its entries (transactions + opening balance), detaches docs, resets voucher_sequences, marks the row \'undone\' so the file can be re-imported. Use after a botched import. Period must be open. HIGH risk.',
inputSchema: {
type: 'object',
@@ -7716,6 +7799,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_create_voucher',
title: 'Create Manual Voucher (Verifikation)',
description: 'Stage a manual verifikation with arbitrary balanced lines. Use for capitalization (1010), period-end accruals, FX adjustments, rättelseposter outside categorize_transaction. Pass inbox_item_id to book a kvitto direct — links inbox + attaches doc. HIGH risk.',
inputSchema: {
type: 'object',
@@ -7951,7 +8035,8 @@ export const tools: McpTool[] = [
{
name: 'gnubok_correct_entry',
description: 'Stage a rättelse for a posted verifikation per BFL 5 kap 5§ — storno + new corrected entry in the original period (never in-place edit). Use for partial fixes like 2641 → 2614/2645 while preserving the expense leg. Account drives momsdeklaration ruta, not tax_code. HIGH risk.',
title: 'Correct Posted Entry (Rättelse)',
description: 'Stage a rättelse for a posted verifikation per BFL 5 kap 5§ — storno + corrected entry in the original period (never in-place edit). Use for partial fixes like 2641 → 2614/2645. Account drives ruta. HIGH risk.',
inputSchema: {
type: 'object',
additionalProperties: false,
@@ -8112,6 +8197,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_reverse_journal_entry',
title: 'Reverse Journal Entry (Storno)',
description: 'Stage a storno: inverts debits/credits; original stays visible per BFL 5 kap. Use only when the affärshändelse should never have been booked (duplicate, ghost, test). If booked wrong, use gnubok_correct_entry; for refunds, gnubok_credit_invoice. HIGH risk.',
inputSchema: {
type: 'object',
@@ -8264,6 +8350,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_propose_dispositioner',
title: 'Propose Year-End Dispositioner',
description:
'Read-only proposal of bokslutsdispositioner for a fiscal period: periodiseringsfond (avsättning + obligatorisk återföring), överavskrivningar, SLP, bolagsskatt. Call before staging postings.',
inputSchema: {
@@ -8289,6 +8376,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_propose_accruals',
title: 'Propose Accruals (Periodiseringar)',
description:
'Read-only proposal of periodiseringar (förutbetalda/upplupna kostnader). Currently surfaces the vacation-liability change; manual prepaid/accrued entries are submitted by the UI form.',
inputSchema: {
@@ -8311,6 +8399,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_propose_annual_depreciation',
title: 'Propose Annual Depreciation (Avskrivning)',
description:
'Read-only per-asset planenlig avskrivning proposal for a fiscal period. Reads the asset register and existing depreciation schedules. Call before staging the post.',
inputSchema: {
@@ -8333,6 +8422,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_post_annual_depreciation',
title: 'Post Annual Depreciation (Avskrivning)',
description:
'Stage planenlig avskrivning posts — one journal entry per asset for independent reversibility. Mid-risk, always staged.',
inputSchema: {
@@ -8417,6 +8507,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_preview_arsredovisning',
title: 'Preview Annual Report (Årsredovisning)',
description:
'Read-only K2 årsredovisning preview for a fiscal period. Returns flerårsöversikt, eget-kapital-förändring, RR, BR, K2 noter, signature slots. PDF download is via UI.',
inputSchema: {
@@ -8439,6 +8530,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_preview_ef_declaration',
title: 'Preview EF Declaration (NE-bilaga)',
description:
'Read-only EF declaration preview: egenavgifter schablonavdrag, räntefördelning, periodiseringsfond, expansionsfond. All declaration-only, never booked. Pass kapitalunderlag and prior-year amounts as inputs.',
inputSchema: {
@@ -8482,6 +8574,7 @@ export const tools: McpTool[] = [
// Mirrors the /pending web UI for agents that self-review before committing.
{
name: 'gnubok_list_pending_operations',
title: 'List Pending Operations',
description: 'List staged pending_operations. Filter by status (default pending), risk_level, or operation_type. Use to review the queue before calling gnubok_approve_pending_operation or gnubok_reject_pending_operation.',
inputSchema: {
type: 'object',
@@ -8539,6 +8632,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_approve_pending_operation',
title: 'Approve Pending Operation',
description: "Commit a staged pending_operation when the user has explicitly authorised the operation_id. risk_level=high requires confirmed=true — surface the BFL 5 kap 5§ irreversibility to the user first. The /pending web UI offers an equivalent commit path.",
inputSchema: {
type: 'object',
@@ -8679,6 +8773,7 @@ export const tools: McpTool[] = [
{
name: 'gnubok_reject_pending_operation',
title: 'Reject Pending Operation',
description: 'Reject a staged pending_operation without executing it. Status flips to rejected; no journal entries, invoices, or other side-effects created. Idempotent on already-resolved ops (returns 409).',
inputSchema: {
type: 'object',
@@ -8778,6 +8873,7 @@ export const tools: McpTool[] = [
// ── Bring-your-own-extraction for inbox items ────────────────
{
name: 'gnubok_set_inbox_extracted_data',
title: 'Set Inbox Extracted Data',
description: 'Replace extracted_data on an inbox item with agent-supplied fields (bring-your-own-extraction). Use when your own pipeline parses the document better than Accounted\'s OCR. Follow with gnubok_create_supplier_invoice_from_inbox to stage.',
inputSchema: {
type: 'object',
@@ -9305,6 +9401,7 @@ export async function handleMcpRequest(request: Request): Promise<Response> {
jsonRpc(id ?? null, {
tools: allowedTools.map((t) => ({
name: t.name,
...(t.title ? { title: t.title } : {}),
description: t.description,
inputSchema: t.inputSchema,
...(t.outputSchema ? { outputSchema: t.outputSchema } : {}),
+78
View File
@@ -0,0 +1,78 @@
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.
Accounted ships an [MCP](https://modelcontextprotocol.io) server that exposes the full bookkeeping engine — 90+ tools — to any MCP client. The endpoint is:
\`\`\`
https://gnubok.app/api/extensions/ext/mcp-server/mcp
\`\`\`
There are two ways to connect, depending on your client. Both 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 / Claude Desktop custom connector (OAuth 2.1)
Best for most users. No API key to manage — you authorise Accounted the same way you'd authorise any OAuth app.
1. In **claude.ai** (Settings → Connectors) or **Claude Desktop** (Settings → Connectors → Add custom connector), choose **Add custom connector**.
2. Paste the connector URL:
\`\`\`
https://gnubok.app/api/extensions/ext/mcp-server/mcp
\`\`\`
3. Claude opens the Accounted OAuth 2.1 consent screen. Sign in and pick the company you want Claude to act on.
4. On the consent screen you grant **read-only scopes by default** (list invoices, read reports, compute VAT). Write scopes (create invoice, categorise, book vouchers, run year-end) are **listed separately and must be ticked explicitly** — leave them unchecked for a read-only review session.
5. Approve. Claude now lists the Accounted tools and you can start asking questions.
Because the consent is per-company and scoped, you can connect a read-only key for a reviewer and a separate write-enabled connection for day-to-day bookkeeping.
## Path B — \`npx gnubok-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 at **/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": {
"gnubok": {
"command": "npx",
"args": ["gnubok-mcp"],
"env": {
"GNUBOK_API_KEY": "gnubok_sk_test_..."
}
}
}
}
\`\`\`
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.
## Try these prompts
All three run against the deterministic sandbox seed (use a \`gnubok_sk_test_*\` key or pick the sandbox company on the OAuth consent screen). They exercise the read path end-to-end without booking anything.
1. **"Show my uncategorized bank transactions and suggest categories."**
Claude calls \`gnubok_list_uncategorized_transactions\` then \`gnubok_suggest_categories\` and walks you through the proposals. Approving one stages a \`gnubok_categorize_transaction\` pending operation — nothing is booked until you confirm.
2. **"Which invoices are overdue?"**
Claude calls \`gnubok_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 \`gnubok_get_vat_report\` for the momsdeklaration rutor, then \`gnubok_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 (read-only scopes only) or Path B 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; \`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 or B), and the tool name from any error message.
`
+1
View File
@@ -27,6 +27,7 @@ export const DOCS_NAV: DocsNavSection[] = [
{ label: 'Introduction', href: '/docs/api', summary: 'What the Accounted REST API is and how to authenticate.' },
{ label: 'Quickstart', href: '/docs/api/cookbook/quickstart', summary: 'Send your first invoice in five minutes.' },
{ label: 'Authentication', href: '/docs/api#authentication', summary: 'API keys, scopes, test mode.' },
{ label: 'Connect with Claude', href: '/docs/api/connect-claude', summary: 'Connect Claude via the MCP server — OAuth connector or npx gnubok-mcp bridge.' },
],
},
{
+56
View File
@@ -0,0 +1,56 @@
# gnubok-mcp
Connect [Claude Desktop](https://claude.ai/download) (or any stdio MCP client) to your [Accounted](https://gnubok.app) bookkeeping account. This is a thin stdio → HTTPS bridge: it forwards JSON-RPC over stdio to the hosted Accounted MCP server, which exposes 90+ bookkeeping tools (invoices, transactions, VAT/momsdeklaration, payroll, reports, year-end).
Write tools stage a pending operation that you confirm before anything is booked — the bridge never books on its own.
## Quickstart
1. Mint an API key in the Accounted dashboard at **[/settings/api](https://app.gnubok.se/settings?tab=api)**. Use a `gnubok_sk_test_*` key against the sandbox while you evaluate; switch to `gnubok_sk_live_*` for real data. The key's scopes gate which tools are callable.
2. Run the bridge with the key in the environment:
```bash
GNUBOK_API_KEY=gnubok_sk_test_... npx gnubok-mcp
```
It reads JSON-RPC from stdin and writes responses to stdout, so you normally point an MCP client at it rather than running it by hand.
## Claude Desktop config
Add the bridge to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"gnubok": {
"command": "npx",
"args": ["gnubok-mcp"],
"env": {
"GNUBOK_API_KEY": "gnubok_sk_test_..."
}
}
}
}
```
Restart Claude Desktop. The Accounted tools appear in the client and you can start asking questions like *"Show my uncategorized bank transactions and suggest categories."*
### Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `GNUBOK_API_KEY` | yes | — | Your `gnubok_sk_*` API key. |
| `GNUBOK_URL` | no | `https://app.gnubok.se/api/extensions/ext/mcp-server/mcp` | Override the MCP endpoint (e.g. for self-hosted Accounted). |
## Alternative: claude.ai connector (no API key)
If you use **claude.ai** or Claude Desktop's custom-connector flow, you can skip this bridge entirely and add Accounted as an OAuth 2.1 custom connector instead — paste the connector URL `https://gnubok.app/api/extensions/ext/mcp-server/mcp` and authorise on the Accounted consent screen (read-only scopes by default; write scopes are ticked explicitly).
## Docs
Full setup, sample prompts, and a 10-minute reviewer test: **[Connect with Claude](https://gnubok.app/docs/api/connect-claude)**.
## License
MIT
+2 -1
View File
@@ -22,6 +22,7 @@
"node": ">=18"
},
"files": [
"index.mjs"
"index.mjs",
"README.md"
]
}