diff --git a/extensions/general/mcp-server/__tests__/strict-schemas.test.ts b/extensions/general/mcp-server/__tests__/strict-schemas.test.ts index bc11d930..89bcc5d4 100644 --- a/extensions/general/mcp-server/__tests__/strict-schemas.test.ts +++ b/extensions/general/mcp-server/__tests__/strict-schemas.test.ts @@ -41,4 +41,18 @@ describe('MCP tool inputSchema strictness', () => { expect(missing).toEqual([]) }) + + it('every widget-bearing tool is read-only: Claude.ai drops write-annotated interactive tools', () => { + // A tool with definition-level _meta.ui renders on every call. Claude.ai + // accepts that only for read-only tools and silently DROPS a + // write-annotated one from the connector (E2E #9, 2026-08-26: the SIE + // drop card flapped into the Interactive list and vanished). Widget + // tools mint links/lists only; actual writes go through separate + // approval-gated tools the widget calls. + const writers = tools + .filter((t) => (t as { _meta?: { ui?: unknown } })._meta?.ui !== undefined) + .filter((t) => t.annotations.readOnlyHint !== true) + .map((t) => t.name) + expect(writers).toEqual([]) + }) }) diff --git a/extensions/general/mcp-server/server.ts b/extensions/general/mcp-server/server.ts index da6d28cb..7df34403 100644 --- a/extensions/general/mcp-server/server.ts +++ b/extensions/general/mcp-server/server.ts @@ -16387,7 +16387,15 @@ export const tools: McpTool[] = [ }, _meta: { ui: { resourceUri: 'ui://sie-drop/app.html' } }, annotations: { - readOnlyHint: false, + // readOnlyHint MUST stay true on widget-bearing tools: Claude.ai + // accepts always-render widgets only on read-only tools and DROPS a + // write-annotated one from the connector entirely (the tool flapped + // into the Interactive list and vanished, E2E #9 2026-08-26; every + // surviving widget tool was readOnly). Honest too: this only mints a + // short-lived upload URL; the actual write is the staged + // gnubok_import_sie, same shape as receipt_matcher (read-only tool, + // writes via separate approval-gated tools). + readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false,