modelcontextprotocol / modelcontextprotocol/typescript-sdk

Invalid Request frames silently dropped with no error response — client hangs waiting for id that never resolves

Open
#2,247 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug fix proposed P2 ready for work
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-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:

# 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 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 — 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.