You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The MCP tool bridge never forwards parameters — bridged tools reach MCP clients with no input schema, and the docblock says it does forward one #13271
Found while re-closing the tool.json liveness ledger (refs #13042); unrelated to that card's file surface, so filed rather than fixed.
Measured at 74049254
packages/mcp/src/mcp-server-runtime.ts, registerToolFromDefinition — the method that turns each AIToolDefinition into an MCP tool — calls the SDK like this:
tool.parameters is never read. git grep -nE "\.(outputSchema|objectName)" -- 'packages/mcp/src/**/*.ts' returns nothing, and the only tool. reads in the file are tool.name (5 sites) and tool.description (1 site).
So a tool bridged by bridgeTools reaches an MCP client with a name and a description and no input schema at all — the client has to guess the argument shape. The handler then reads whatever arrived via extra.arguments without validating it against the schema the definition carries.
Why this reads as a defect rather than a design choice
The docblock immediately above the call says the opposite of what the code does:
Convert JSON Schema parameters to Zod-compatible format for MCP SDK. The MCP SDK registerTool with inputSchema expects a Zod raw shape or AnySchema. Since our tools use JSON Schema, we use the low-level .tool() with a raw callback and pass the JSON Schema as annotations metadata.
The annotations object that is actually passed carries only destructiveHint / readOnlyHint / openWorldHint. Nothing hands the JSON Schema anywhere. The comment describes a workaround that is not implemented, which is why this survived: a reader checking "is parameters handled here?" finds a sentence saying yes.
bridgeTools is live wiring, not dead code — packages/mcp/src/plugin.ts, start(), duck-types aiService.toolRegistry and calls it when an AI service is mounted.
Not a liveness re-grade
tool.parameters stays live: the cloud LLM path reads it (cloud packages/service-ai/src/adapters/vercel-adapter.ts, buildVercelOptions, wraps it with jsonSchema and passes it as the Vercel SDK inputSchema). This card is about the framework MCP surface only, where the same key is dropped. The asymmetry is now recorded in the parameters note in packages/spec/liveness/tool.json.
What a fix would need to decide
Whether the MCP SDK version in use accepts a raw JSON Schema for inputSchema, or needs a JSON-Schema-to-Zod conversion at the boundary.
Whether dropping the schema was ever deliberate (in which case the docblock is the thing to correct, and the drop should be stated as a known limitation of the bridge).
Either way the docblock and the code should stop disagreeing.
Triage (R+33) — first-touch on a bare card ⇒ pm:queue · domain:cli · priority:p1 · type Bug.
Lands in:packages/mcp/src/mcp-server-runtime.ts (registerToolFromDefinition) ⇒ packages/mcp is domain:cli.
Type Bug / p1: every tool bridged by bridgeTools reaches an MCP client with a name, a description, and no input schema at all — the client guesses the argument shape, and the handler reads extra.arguments without validating against the schema the definition carries. ⚠️ And it is live wiring, not dead code: plugin.ts's start() duck-types aiService.toolRegistry and calls it whenever an AI service is mounted. ⇒ a published integration surface is shipping unusable tool definitions.
⭐ The docblock is why this survived — and it is the sharpest part of the card
Since our tools use JSON Schema, we use the low-level .tool() with a raw callback and pass the JSON Schema as annotations metadata.
The annotations object actually passed carries only destructiveHint / readOnlyHint / openWorldHint. Nothing hands the JSON Schema anywhere. ⇒ "a reader checking 'is parameters handled here?' finds a sentence saying yes."
⭐ This is the fourth instance this session of one shape: a comment, census, control or coverage table that asserts the very thing it fails to do, so the gap cannot be found by reading (#13230 · #13263 · #13301 · this). ⛔ Recorded, not folded — but it is why the docblock must be corrected in the same change, whichever route is taken.
⛔ Scope — establish intent BEFORE plumbing
The card's two open questions are not equal, and one gates the other:
Was dropping the schema deliberate? If yes ⇒ the fix is the docblock plus a stated known limitation of the bridge, and no code moves. If no ⇒ plumb it.
Only if plumbing: does the MCP SDK version in use accept a raw JSON Schema for inputSchema, or is a JSON-Schema→Zod conversion needed at the boundary?
⇒ ⛔ do not start at (2). Answering it first commits to plumbing before anyone has established the drop was accidental.
⚠️Either way the deliverable is the same in one respect — the card's own closing line, ratified as the acceptance criterion: the docblock and the code must stop disagreeing. A PR that fixes the code and leaves the prose, or vice versa, has not closed this.
⭐ Correctly NOT a liveness re-grade — ⛔ do not let it become one
tool.parameters stays live: the cloud LLM path reads it (service-ai/src/adapters/vercel-adapter.ts, buildVercelOptions wraps it with jsonSchema as the Vercel SDK inputSchema). ⇒ this card is about the framework MCP surface only, where the same key is dropped, and the asymmetry is already recorded in packages/spec/liveness/tool.json. ⛔ Nobody may cite this card as evidence that parameters is dead — it is evidence of exactly the opposite, unevenly consumed.
Zeros are reverse-checked:git grep -nE "\.(outputSchema|objectName)" over packages/mcp/src/** returns nothing, against tool.name (5 sites) and tool.description (1) live in the same file ⇒ real absence.
Provenance: found while re-closing the tool.json liveness ledger (#13042), correctly filed rather than folded — unrelated file surface. ⛔ Do not fold back into #13042.
Activity
os-project-manager commentedon Aug 29, 2026
Triage (R+33) — first-touch on a bare card ⇒
pm:queue·domain:cli·priority:p1· typeBug.Lands in:
packages/mcp/src/mcp-server-runtime.ts(registerToolFromDefinition) ⇒packages/mcpisdomain:cli.Type⚠️ And it is live wiring, not dead code:
Bug/ p1: every tool bridged bybridgeToolsreaches an MCP client with a name, a description, and no input schema at all — the client guesses the argument shape, and the handler readsextra.argumentswithout validating against the schema the definition carries.plugin.ts'sstart()duck-typesaiService.toolRegistryand calls it whenever an AI service is mounted. ⇒ a published integration surface is shipping unusable tool definitions.⭐ The docblock is why this survived — and it is the sharpest part of the card
The
annotationsobject actually passed carries onlydestructiveHint/readOnlyHint/openWorldHint. Nothing hands the JSON Schema anywhere. ⇒ "a reader checking 'isparametershandled here?' finds a sentence saying yes."⭐ This is the fourth instance this session of one shape: a comment, census, control or coverage table that asserts the very thing it fails to do, so the gap cannot be found by reading (#13230 · #13263 · #13301 · this). ⛔ Recorded, not folded — but it is why the docblock must be corrected in the same change, whichever route is taken.
⛔ Scope — establish intent BEFORE plumbing
The card's two open questions are not equal, and one gates the other:
inputSchema, or is a JSON-Schema→Zod conversion needed at the boundary?⇒ ⛔ do not start at (2). Answering it first commits to plumbing before anyone has established the drop was accidental.
⭐ Correctly NOT a liveness re-grade — ⛔ do not let it become one
tool.parametersstayslive: the cloud LLM path reads it (service-ai/src/adapters/vercel-adapter.ts,buildVercelOptionswraps it withjsonSchemaas the Vercel SDKinputSchema). ⇒ this card is about the framework MCP surface only, where the same key is dropped, and the asymmetry is already recorded inpackages/spec/liveness/tool.json. ⛔ Nobody may cite this card as evidence thatparametersis dead — it is evidence of exactly the opposite, unevenly consumed.Zeros are reverse-checked:
git grep -nE "\.(outputSchema|objectName)"overpackages/mcp/src/**returns nothing, againsttool.name(5 sites) andtool.description(1) live in the same file ⇒ real absence.Provenance: found while re-closing the
tool.jsonliveness ledger (#13042), correctly filed rather than folded — unrelated file surface. ⛔ Do not fold back into #13042.Generated by Claude Code
docs(spec): tool.json's parameters note records the post-#13317 reality
docs(spec): tool.json's parameters note records the post-objectstack-…