modelcontextprotocol / modelcontextprotocol/typescript-sdk
Invalid Request frames silently dropped with no error response — client hangs waiting for id that never resolves
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
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-everythingv2.0.0@modelcontextprotocol/server-memoryv0.6.3
Both use SDK v1.29.0.
SDK version
@modelcontextprotocol/sdk 1.29.0
To Reproduce
Case 1 — request with missing jsonrpc field:
# 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:
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:
{"jsonrpc":"2.0","id":99,"error":{"code":-32600,"message":"Invalid Request"}}
Case 2 — id is not recoverable from an array, so use null:
{"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:
// 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
jsonrpcfield: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 — 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/server/stdio.ts at processReadBuffer(), then trace ReadBuffer.readMessage() and JSONRPCMessageSchema validation. Reproduce both malformed-message cases with the provided stdio commands. Done means invalid requests produce -32600 error responses with the recoverable id or null instead of being silently dropped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100