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:
this.mcpServer.registerTool(
tool.name,
{
description: tool.description,
annotations: {
destructiveHint: this.isDestructiveTool(tool.name),
readOnlyHint: this.isReadOnlyTool(tool.name),
openWorldHint: false,
},
},
async (extra) => { /* reads extra.arguments */ },
);
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.
Found while re-closing the
tool.jsonliveness ledger (refs #13042); unrelated to that card's file surface, so filed rather than fixed.Measured at
74049254packages/mcp/src/mcp-server-runtime.ts,registerToolFromDefinition— the method that turns eachAIToolDefinitioninto an MCP tool — calls the SDK like this:tool.parametersis never read.git grep -nE "\.(outputSchema|objectName)" -- 'packages/mcp/src/**/*.ts'returns nothing, and the onlytool.reads in the file aretool.name(5 sites) andtool.description(1 site).So a tool bridged by
bridgeToolsreaches 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 viaextra.argumentswithout 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:
The
annotationsobject that is actually passed carries onlydestructiveHint/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 "isparametershandled here?" finds a sentence saying yes.bridgeToolsis live wiring, not dead code —packages/mcp/src/plugin.ts,start(), duck-typesaiService.toolRegistryand calls it when an AI service is mounted.Not a liveness re-grade
tool.parametersstayslive: the cloud LLM path reads it (cloud packages/service-ai/src/adapters/vercel-adapter.ts,buildVercelOptions, wraps it withjsonSchemaand passes it as the Vercel SDKinputSchema). This card is about the framework MCP surface only, where the same key is dropped. The asymmetry is now recorded in theparametersnote inpackages/spec/liveness/tool.json.What a fix would need to decide
inputSchema, or needs a JSON-Schema-to-Zod conversion at the boundary.Either way the docblock and the code should stop disagreeing.