When the SDK receives a JSON-RPC message that fails schema validation — specifically a request with a missing `jsonrpc` field, or a batch array — it silently drops the message with no response. Any client tracking pending requests by id hangs indefinitely because the server consumed the request but returned nothing. This affects every server built on `@modelcontextprotocol/sdk`. Verified on: - `@modelcontextprotocol/server-everything` v2.0.0 - `@modelcontextprotocol/server-memory` v0.6.3 Both use SDK v1.29.0. ## SDK version ``` @modelcontextprotocol/sdk 1.29.0 ``` ## To Reproduce **Case 1 — request with missing `jsonrpc` field:** ```bash # Start any stdio MCP server, send a valid handshake then the malformed request printf '%s\n%s\n%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '{"id":99,"method":"tools/list","params":{}}' \ | npx -y @modelcontextprotocol/server-everything stdio ``` Observe: no response for id `99`. The server responds to id `1` and then goes silent. **Case 2 — batch request:** ```bash printf '%s\n%s\n%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '[{"jsonrpc":"2.0","id":100,"method":"tools/list"},{"jsonrpc":"2.0","id":101,"method":"ping"}]' \ | npx -y @modelcontextprotocol/server-everything stdio ``` Observe: no response for ids `100` or `101`. ## Expected behavior Per JSON-RPC 2.0 §5, when a server receives a message with a detectable `id` but invalid structure, it must return an error response: **Case 1** — id is recoverable from the raw JSON, so: ```json {"jsonrpc":"2.0","id":99,"error":{"code":-32600,"message":"Invalid Request"}} ``` **Case 2** — id is not recoverable from an array, so use `null`: ```json {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid Request"}} ``` Either way, the client gets a response and can unblock the pending request. ## Actual behavior Zero bytes returned. The client's pending request for the id sits unresolved until timeout or connection close. ## Root cause In `src/server/stdio.ts`, `processReadBuffer()` catches schema validation errors and routes them to `onerror` — but never sends a response: ```typescript // sdk/src/server/stdio.ts processReadBuffer() { while (true) { try { const message = this._readBuffer.readMessage(); // readMessage() calls JSONRPCMessageSchema.parse(JSON.parse(line)) // ↑ throws ZodError for missing jsonrpc field or batch array this.onmessage?.(message); } catch (error) { this.onerror?.(error); // ← error reported, but no response sent } } } ``` The Zod errors thrown: - Missing `jsonrpc` field: `Invalid input: expected "2.0" at path jsonrpc` - Batch array: `Invalid input: expected object, received array` Both cases discard the `id` without sending a `-32600` response. ## How I found this I ran [mcp-scope](https://github.com/SSanju/mcp-scope) — a passive JSON-RPC capture tool for MCP — in front of a local server-everything instance and used `mcp-scope check --strict` on the capture: ``` [WARN ] unmatched request id=99 (no response in capture) [ERROR] unparseable JSON-RPC frame ← the batch array FAIL — 1 error(s), 1 warning(s) ``` The capture shows the request was sent and received (it appears in the c2s direction) but no s2c response frame ever arrived. Capture file: https://github.com/SSanju/mcp-scope/blob/main/examples/mcp-toolbox/capture-server-everything.jsonl