fix(mcp): promote gnubok_update_customer to the default tools/list catalog (#1986)
* 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>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
17caf9d80a
commit
89d0e1b994
@@ -21,7 +21,7 @@ For internal Anthropic API usage (the SDK is called from `lib/ai/provider.ts` an
|
||||
|
||||
## Payload-size watchdog
|
||||
|
||||
`payload-size.bench.test.ts` enforces a `tools/list` JSON payload ceiling. If the test fires, the right answer is rarely "raise the ceiling". Instead, trim descriptions or set specialized wide tools to `catalogVisibility: 'search'`. Those tools remain discoverable with full schemas through `gnubok_search_tools` and callable through `tools/call` without bloating the default catalog.
|
||||
`payload-size.bench.test.ts` enforces a `tools/list` JSON payload ceiling. If the test fires, the right answer is rarely "raise the ceiling". Instead, trim descriptions or set specialized wide tools to `catalogVisibility: 'search'`. Those tools remain discoverable with full schemas through `gnubok_search_tools` and callable through `tools/call` on the wire without bloating the default catalog. Claude.ai only calls tools present in `tools/list`, so a tool that a user or a skill must call directly stays in the default catalog.
|
||||
|
||||
## Where things live
|
||||
|
||||
|
||||
@@ -278,12 +278,20 @@ describe('tools/list payload size guard', () => {
|
||||
// skatteverket_connection, recommended_tools: agent-briefing.test.ts
|
||||
// pins their RUNTIME shape, so nothing was left unguarded), against
|
||||
// +~245 for the new gnubok_call_tool.
|
||||
// * 63.1K to 63.8K with gnubok_update_customer promoted to the default
|
||||
// catalog (#1706, #1876). gnubok_call_tool (above) only bridges READ
|
||||
// tools and refuses writes, so a search-only WRITE tool is still
|
||||
// uncallable on Claude.ai; the reporter read the tool as missing twice.
|
||||
// The +761 tokens are the 19-property partial-update wire contract plus
|
||||
// the staging envelope; the description is already 152 chars.
|
||||
// Measured 63 761 on the accounted projection after merging #1993
|
||||
// (line_type and revenue_account declared on the create item schema).
|
||||
// Long-term answer to growth is no longer a ceiling bump. gnubok_call_tool
|
||||
// makes `catalogVisibility: 'search'` usable for READ tools on hosts that
|
||||
// can only invoke what tools/list showed them, which is the constraint that
|
||||
// forced gnubok_reconcile_match back into the default catalog on
|
||||
// 2026-08-26. Demote a read to search-only before proposing a bump.
|
||||
expect(approxTokens).toBeLessThan(63_100)
|
||||
expect(approxTokens).toBeLessThan(63_800)
|
||||
})
|
||||
|
||||
it('keeps the accounted_* namespace as the measured worst case', () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TOOL_SCOPE_MAP } from '@/lib/auth/api-keys'
|
||||
import { OPERATION_RISK_TIERS } from '@/lib/pending-operations/risk-tiers'
|
||||
import { hashRequest } from '@/lib/api/idempotency'
|
||||
import { decryptPersonnummer, encryptPersonnummer } from '@/lib/salary/personnummer'
|
||||
import { tools } from '../server'
|
||||
import { tools, isDefaultCatalogTool } from '../server'
|
||||
|
||||
const CUSTOMER_ID = '11111111-1111-4111-8111-111111111111'
|
||||
const tool = () => tools.find((candidate) => candidate.name === 'gnubok_update_customer')!
|
||||
@@ -38,7 +38,8 @@ describe('gnubok_update_customer: registration', () => {
|
||||
expect(tool().inputSchema.additionalProperties).toBe(false)
|
||||
expect(tool().annotations.readOnlyHint).toBe(false)
|
||||
expect(tool().annotations.idempotentHint).toBe(true)
|
||||
expect(tool().catalogVisibility).toBe('search')
|
||||
expect(isDefaultCatalogTool(tool())).toBe(true)
|
||||
expect(tools.filter(isDefaultCatalogTool).map((t) => t.name)).toContain('gnubok_update_customer')
|
||||
expect(TOOL_SCOPE_MAP.gnubok_update_customer).toBe('customers:write')
|
||||
expect(OPERATION_RISK_TIERS.update_customer).toBe('low')
|
||||
})
|
||||
@@ -48,7 +49,7 @@ describe('gnubok_update_customer: registration', () => {
|
||||
expect(properties.personal_number).toMatchObject({ type: ['string', 'null'] })
|
||||
})
|
||||
|
||||
it('keeps the wide write schema discoverable through tool search', async () => {
|
||||
it('is also discoverable through tool search', async () => {
|
||||
const search = tools.find((candidate) => candidate.name === 'gnubok_search_tools')!
|
||||
const result = (await search.execute(
|
||||
{
|
||||
|
||||
@@ -5776,7 +5776,6 @@ export const tools: McpTool[] = [
|
||||
idempotentHint: true,
|
||||
openWorldHint: false,
|
||||
},
|
||||
catalogVisibility: 'search',
|
||||
async execute(args, companyId, userId, supabase, actor) {
|
||||
const changes: Record<string, unknown> = {}
|
||||
for (const key of [
|
||||
|
||||
Reference in New Issue
Block a user