* fix(mcp): promote gnubok_update_customer to the default tools/list catalog
The tool existed since #1876 but was catalogVisibility 'search', which
means it appeared only through gnubok_search_tools. Claude.ai (and other
list-driven clients) can only call tools present in tools/list, so on the
primary connector the tool was uncallable and the reporter of #1706 read
it as missing twice.
- server.ts: drop the 'search' flag on gnubok_update_customer; no change
to schema, executor, scopes, risk tier, or staging path
- update-customer.test.ts: pin the tools/list projection
(isDefaultCatalogTool) instead of the flag; keep the search test
- payload-size.bench.test.ts: ceiling 63_400 to 64_100 (measured 64 043,
+761 over main) with a progression bullet
- README.md: note that search-only tools are not callable from Claude.ai
- DECISIONS.md: supersede the 2026-08-25 keep-search-only entry
Closes#1706
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2
* test(mcp): re-measure the tools/list ceiling after #1993 landed on main
#1993 declared line_type and revenue_account on the create item schema,
which moved the accounted projection to 63 761 with gnubok_update_customer
promoted; ceiling 63_800 keeps the same headroom as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
gnubok_create_customer takes a personnummer (encrypted before approval)
but gnubok_update_customer did not, so an existing customer whose
personnummer sat in the org-number field could not be corrected via MCP.
The REST PATCH already supports it; this closes the MCP/pending-operations
gap across its three layers:
- tool inputSchema: personal_number (string or null) on the strict
whitelist. The tool validates the plaintext before any DB read and
mirrors the REST PATCH semantics: masked echo (********-1234 or
********-????) = leave unchanged, explicit null = clear, absent =
untouched. Setting is refused unless the row ends up as an individual
(GDPR art. 5.1 c), including via a simultaneous type change.
- CustomerChangesSchema: personal_number_encrypted (nullable, ciphertext
shape per customers_personal_number_check 20260726110000). The
plaintext key stays forbidden by .strict() and staging-pii-guard.
- update executor: maps the staged ciphertext onto customers
.personal_number (set/clear/leave), re-checks the individual-only rule
against a tampered row, and returns only personal_number_masked.
PII handling: the personnummer is encrypted at staging time
(AES-256-GCM, same path as create); pending_operations params carry only
the ciphertext and the approval preview only the masked form. Idempotency
hashing switches to the masked preview for personnummer-bearing updates
(random-IV ciphertext would break retries); other updates keep their
previous hash identity.
catalogVisibility stays 'search': tools/list is at its 59.95K token
ceiling with zero headroom (see DECISIONS.md).
Fixes#1876
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>