fix(mcp): reconcile_match callable + two-phase efterkontroll + instant value for web-onboarded users (#1967)

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-26 23:04:57 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent b160f7290d
commit dae0aac26a
4 changed files with 49 additions and 19 deletions
@@ -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)
})
})
@@ -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({
+6 -5
View File
@@ -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,
@@ -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