From dae0aac26a3180dccb35fb04e467b1a0ea3456b0 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg Date: Wed, 26 Aug 2026 23:04:57 +0200 Subject: [PATCH] fix(mcp): reconcile_match callable + two-phase efterkontroll + instant value for web-onboarded users (#1967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(plugin): starter prompt nudges the connector's onboarding guide Several E2E runs composed the first reply from tool descriptions before the skill loaded (questionnaire instead of the guided round). Four words in the published prompt point the agent at the guide from message one; the rest of the prompt stays memory-first and universal. Co-Authored-By: Claude Fable 5 * fix(mcp): reconcile_match into the default catalog + two-phase efterkontroll + instant value for web-onboarded users E2E #12 (the smoothest run yet) surfaced three things: 1. gnubok_reconcile_match was search-only, so Claude.ai could not call it: the agent misread the uncallable tool as a missing reconciliation:write scope and punted the entire dont-double-book matching step to the web app. Promoted to the default catalog (the standing rule: skill-instructed tools must be callable); ceiling 63K to 63.4K documented. 2. Skill: efterkontroll split into two phases per founder direction. Step 3b runs DIRECTLY after the import commits: verify the trial balance, sanity-read the content, and PREPARE the chart (create 1630/8423/8314/6992 when the SIE lacks them) so the bank and Skatteverket connections land in a book that is ready for them. Step 4b after the connections: match SIE-covered bank rows with reconcile_match (never re-categorize), reconcile skattekontot to the öre, names, underlag, in a prioritized numbered list. 3. Skill: users who onboarded via the web app and connect the MCP with everything already set up get the reconciliation pass and Att göra-list as the FIRST reply instead of setup steps: immediate value regardless of onboarding path. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 --- .../__tests__/payload-size.bench.test.ts | 6 ++- .../__tests__/reconciliation-tools.test.ts | 6 ++- extensions/general/mcp-server/server.ts | 11 ++--- .../general/mcp-server/skills/onboarding.ts | 45 ++++++++++++++----- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/extensions/general/mcp-server/__tests__/payload-size.bench.test.ts b/extensions/general/mcp-server/__tests__/payload-size.bench.test.ts index c9fbf758..8ad32f88 100644 --- a/extensions/general/mcp-server/__tests__/payload-size.bench.test.ts +++ b/extensions/general/mcp-server/__tests__/payload-size.bench.test.ts @@ -238,9 +238,13 @@ describe('tools/list payload size guard', () => { // connect card, same one-click feel as bank/Skatteverket): the skill // instructs calling it when the user names Fortnox/BL/Briox/Wint, so // default catalog for the standing Claude.ai reason. + // * 63K to 63.4K with gnubok_reconcile_match promoted to the default + // catalog: the onboarding efterkontroll instructs matching bank rows + // against SIE verifikat, and a search-only tool is uncallable on + // Claude.ai (E2E #12 punted to the web app over it). // 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(63_000) + expect(approxTokens).toBeLessThan(63_400) }) }) diff --git a/extensions/general/mcp-server/__tests__/reconciliation-tools.test.ts b/extensions/general/mcp-server/__tests__/reconciliation-tools.test.ts index 16c9f791..7d6d834b 100644 --- a/extensions/general/mcp-server/__tests__/reconciliation-tools.test.ts +++ b/extensions/general/mcp-server/__tests__/reconciliation-tools.test.ts @@ -1,6 +1,6 @@ /** * The account-keyed reconciliation tools: status (account_key branch), items, - * reconcile_match (stages; dry_run previews), reconcile_unmatch (search-only). + * reconcile_match (stages; dry_run previews; default catalog), reconcile_unmatch (search-only). * Service functions are mocked; the staging path runs for real in dry_run * mode (no insert), so the STAGED_OPERATION_SCHEMA contract is exercised. */ @@ -60,7 +60,9 @@ describe('reconciliation MCP tools', () => { expect(isDefaultCatalogTool(tool('gnubok_get_reconciliation_status'))).toBe(true) expect(isDefaultCatalogTool(tool('gnubok_list_reconciliation_items'))).toBe(true) // Writes sit behind search to respect the tools/list payload ceiling. - expect(isDefaultCatalogTool(tool('gnubok_reconcile_match'))).toBe(false) + // Default since E2E #12: the onboarding efterkontroll instructs the + // matching step and Claude.ai cannot call search-only tools. + expect(isDefaultCatalogTool(tool('gnubok_reconcile_match'))).toBe(true) expect(isDefaultCatalogTool(tool('gnubok_reconcile_unmatch'))).toBe(false) expect(isDefaultCatalogTool(tool('gnubok_link_transaction_to_journal_entry'))).toBe(false) expect(deriveToolMeta(tool('gnubok_reconcile_match'))).toMatchObject({ diff --git a/extensions/general/mcp-server/server.ts b/extensions/general/mcp-server/server.ts index 7df34403..a5ef505e 100644 --- a/extensions/general/mcp-server/server.ts +++ b/extensions/general/mcp-server/server.ts @@ -10852,7 +10852,7 @@ export const tools: McpTool[] = [ { name: 'gnubok_list_reconciliation_items', title: 'Reconciliation Items', - description: 'Rows behind one account\'s reconciliation bridge, bucketed (proposed, unmatched_external, unmatched_ledger, matched, ignored, upcoming): side, qualified id, amount, proposal with confidence + reasons, allowed actions. Link via gnubok_reconcile_match (search).', + description: 'Rows behind one account\'s reconciliation bridge, bucketed (proposed, unmatched_external, unmatched_ledger, matched, ignored, upcoming): side, qualified id, amount, proposal with confidence + reasons, allowed actions. Link via gnubok_reconcile_match.', inputSchema: { type: 'object', additionalProperties: false, @@ -10905,10 +10905,11 @@ export const tools: McpTool[] = [ name: 'gnubok_reconcile_match', title: 'Reconcile: Link Pairs', description: 'Link outside rows (bank or skattekonto) to existing verifikat on one account; no new bokföring. Pass pairs, or use_proposals to apply the persisted proposals. Stages. dry_run previews.', - // Search-only to stay under the tools/list payload ceiling: the default - // catalog carries the reads (status + items); this write is reached via - // gnubok_search_tools, the close_period loadout and the items tool's hint. - catalogVisibility: 'search', + // Default catalog since E2E #12: the onboarding efterkontroll instructs + // matching SIE-covered bank rows against existing verifikat, and + // Claude.ai cannot call search-only tools: the agent misread the + // uncallable tool as a missing reconciliation:write scope and punted the + // whole matching step to the web app. inputSchema: { type: 'object', additionalProperties: false, diff --git a/extensions/general/mcp-server/skills/onboarding.ts b/extensions/general/mcp-server/skills/onboarding.ts index 4bb9e2d9..1c6edee7 100644 --- a/extensions/general/mcp-server/skills/onboarding.ts +++ b/extensions/general/mcp-server/skills/onboarding.ts @@ -38,6 +38,13 @@ need zero of these questions. - "Lägg till ett nytt bolag" (an existing user adding a second company) - A byrå/consultant onboarding a new client company (pass \`team_id\`) +**Already set up? Deliver value immediately instead.** When +\`gnubok_get_agent_briefing\` shows a company that already exists with +data and connections (the user onboarded via the web app), do NOT walk +the setup steps: go straight to the Step 4b reconciliation pass and the +Att göra-list, and open with the findings: a numbered list with amounts +in your FIRST reply. That first impression is the product. + ## Step 0: connect If this session is not connected yet, the first company-scoped call (for @@ -167,23 +174,39 @@ link, and duplicate raw URLs read as clutter. When the user says they are done (or comes back), re-call \`gnubok_connect_bank\` to verify \`connected\`, then go DIRECTLY to step 5. -## Step 4b: after the import: efterkontroll (this is where trust is won) +## Step 3b: DIRECTLY after the import: verify and PREPARE (before the bank) -Run a short audit pass as soon as history + bank are in, and fix findings -through the normal staged flow, a few lines per finding: +The moment the import commits, run a fast pass so the connections land in +a book that is READY for them: the user should feel value before the bank +even connects. -- \`gnubok_get_trial_balance\`: does the book balance and match the SIE? -- Skattekonto vs 1630: if Skatteverket is connected, reconcile the - skattekonto events against the ledger. Common finds: paid payroll taxes - still standing as liabilities on 2710/2731, the 1630 account missing - entirely, unbooked ränta/avgifter (kostnadsränta 8423, skattefri - intäktsränta 8314, ej avdragsgill förseningsavgift 6992: never the - ordinary cost accounts, or the year-end tax computation goes wrong). +- \`gnubok_get_trial_balance\`: balances, and matches the SIE's UB. +- Sanity-read the content: no income accounts? liabilities that look + already-paid? Say so in one line each; do not fix yet. +- PREPARE the chart for what the connections will bring: if the import + lacks 1630 (skattekonto), 8423 (kostnadsränta skattekonto), 8314 + (skattefri intäktsränta) or 6992 (ej avdragsgilla avgifter), create + them NOW so tax payments and fees book correctly from the first sync. +- Voucher gaps: explain each with \`gnubok_explain_voucher_gap\`. + +## Step 4b: after the connections: reconcile (this is where trust is won) + +Once bank + Skatteverket deliver data, run the reconciliation pass and +fix findings through the normal staged flow, a few lines per finding: + +- Bank rows covered by the SIE period: MATCH them against existing + verifikat with \`gnubok_reconcile_match\` (per account; \`dry_run\` + first): never categorize them again, that double-books salaries and + everything else. Only rows after the SIE's last date get booked fresh. +- Skattekonto vs 1630: reconcile the events against the ledger. Common + finds: paid payroll taxes still standing on 2710/2731, ränta/avgifter + unbooked (8423/8314/6992: never ordinary cost accounts, or the + year-end tax computation goes wrong). End state must match + Skatteverket's saldo to the öre. - Auto-created bank accounts (1930/1931/1935) named after the company: suggest proper names. - Underlag coverage: verifikat over ~5 000 kr without documents (BFL 5 kap 6 §): list them, offer the receipt-matcher flow. -- Voucher gaps: explain each with \`gnubok_explain_voucher_gap\`. Present findings as a short numbered list with amounts, fix in priority order on the user's go-ahead, and re-verify the reconciled balances match