Summary
On @modelcontextprotocol/sdk@1.30.0, a server built with the low-level Server API declares an inputSchema in tools/list, but the SDK does not check tools/call arguments against it before invoking the handler. The call runs and the client receives a well-formed result with no error code and no isError.
McpServer does not have this problem. Its zod-derived schemas are enforced.
The specification's Security Considerations require servers to "Validate all tool inputs", so the low-level path lets a server publish a contract the SDK does not hold it to.
Reproduction
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema }
from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "demo", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "scan_code_imports",
inputSchema: {
type: "object",
properties: { code: { type: "string" } },
required: ["code"],
},
}],
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => ({
content: [{
type: "text",
text: `CLEAN (received typeof=${typeof req.params.arguments?.code})`,
}],
}));
await server.connect(new StdioServerTransport());
Call it with {"name":"scan_code_imports","arguments":{"code":12345}}, where the declared schema requires a string:
{"jsonrpc":"2.0","id":3,"result":{"content":[
{"type":"text","text":"CLEAN (received typeof=number)"}]}}
The handler ran with a number. The same tool built on McpServer with an equivalent zod schema rejects the identical call:
MCP error -32602: Input validation error: Invalid arguments for tool
scan_code_imports: Invalid input: expected string, received number at code
Scale
I ran a protocol conformance census over the official registry, installing and executing all 6,106 self-contained, locally installable servers. 129 executed a tool on an argument their own declared schema rejects and returned ordinary output. Attributing each to its SDK from package metadata:
| SDK family |
count |
| official TypeScript SDK |
112 |
| no recognized SDK package |
7 |
| metadata unavailable |
10 |
| official Python SDK |
0 |
| FastMCP (Python) |
0 |
Responding denominators are 2,347 for official-ts and 1,020 for the two Python families combined. I have not tested the Python SDKs directly, so I report the zero as an observation rather than claiming a mechanism for it.
109 of the 112 declare a caret range such as ^1.0.0 or ^1.29.0, and none declares an exact version. Because the census installed each server fresh, those resolve to the newest 1.x, so this is current 1.x behaviour observed in the wild rather than a legacy artifact. It also means a fix released on the 1.x line reaches essentially all of them on their next install.
The client-visible effect is that the result is indistinguishable from a correct one. In the census a code-scanning tool asked to scan the integer 12345 answered CLEAN, and a prompt-scoring tool returned a full rubric with per-dimension sub-scores for the same integer.
Suggested fix
Validate tools/call arguments against the tool's declared inputSchema before dispatch on the low-level path, matching what McpServer already does, and report failures as isError consistent with SEP-1303.
On @modelcontextprotocol/server@2.0.0
It exports a JsonSchemaValidator and ships an Ajv implementation at ./validators/ajv. I could not determine from the published types whether a validator is wired in by default. If it is opt-in, the same gap exists there for authors who do not supply one.
Artifacts
Harness, dataset, raw transcripts, and the two probe servers used above: https://github.com/Ahmad-Faraj/mcp-conformance (see sdk_probe/)
I can supply the per-server transcripts behind the 112 figure, or re-run the census against a patched build.
Summary
On
@modelcontextprotocol/sdk@1.30.0, a server built with the low-levelServerAPI declares aninputSchemaintools/list, but the SDK does not checktools/callarguments against it before invoking the handler. The call runs and the client receives a well-formed result with no error code and noisError.McpServerdoes not have this problem. Its zod-derived schemas are enforced.The specification's Security Considerations require servers to "Validate all tool inputs", so the low-level path lets a server publish a contract the SDK does not hold it to.
Reproduction
Call it with
{"name":"scan_code_imports","arguments":{"code":12345}}, where the declared schema requires a string:{"jsonrpc":"2.0","id":3,"result":{"content":[ {"type":"text","text":"CLEAN (received typeof=number)"}]}}The handler ran with a number. The same tool built on
McpServerwith an equivalent zod schema rejects the identical call:Scale
I ran a protocol conformance census over the official registry, installing and executing all 6,106 self-contained, locally installable servers. 129 executed a tool on an argument their own declared schema rejects and returned ordinary output. Attributing each to its SDK from package metadata:
Responding denominators are 2,347 for official-ts and 1,020 for the two Python families combined. I have not tested the Python SDKs directly, so I report the zero as an observation rather than claiming a mechanism for it.
109 of the 112 declare a caret range such as
^1.0.0or^1.29.0, and none declares an exact version. Because the census installed each server fresh, those resolve to the newest 1.x, so this is current 1.x behaviour observed in the wild rather than a legacy artifact. It also means a fix released on the 1.x line reaches essentially all of them on their next install.The client-visible effect is that the result is indistinguishable from a correct one. In the census a code-scanning tool asked to scan the integer
12345answeredCLEAN, and a prompt-scoring tool returned a full rubric with per-dimension sub-scores for the same integer.Suggested fix
Validate
tools/callarguments against the tool's declaredinputSchemabefore dispatch on the low-level path, matching whatMcpServeralready does, and report failures asisErrorconsistent with SEP-1303.On
@modelcontextprotocol/server@2.0.0It exports a
JsonSchemaValidatorand ships an Ajv implementation at./validators/ajv. I could not determine from the published types whether a validator is wired in by default. If it is opt-in, the same gap exists there for authors who do not supply one.Artifacts
Harness, dataset, raw transcripts, and the two probe servers used above: https://github.com/Ahmad-Faraj/mcp-conformance (see
sdk_probe/)I can supply the per-server transcripts behind the 112 figure, or re-run the census against a patched build.