MCP stdio transport dropped on unrecognized notifications/progress token, corrupting in-flight tools/call
Summary
Claude Code tears down the entire MCP stdio transport when it receives a notifications/progress whose progressToken it does not (or no longer) recognize. An outbound tools/call that is in flight at that moment is aborted/corrupted, so the server receives the request without its arguments fully intact.
Symptom
A tools/call with a moderately large argument (a string field of ~600+ characters) intermittently fails with a server-side Zod validation error:
body: expected string, received undefined
i.e. the MCP server received the request but the large body field was missing. Immediately before the failure, the Claude Code MCP log shows:
Connection error: Received a progress notification for an unknown token:
{progress:5, total:8, message:"Fusing rankings", progressToken:2}
STDIO connection dropped
Root cause (hypothesis)
On receiving a notifications/progress with an unrecognized/stale progressToken, Claude Code treats it as a connection error and drops the stdio transport. The in-flight outbound tools/call write is aborted/truncated, so the server parses a request with missing fields.
Per the MCP spec, progress notifications are advisory; an unknown or late progress token should be ignored, not cause a transport teardown.
Proven NOT server-side
Same MCP server, same payloads:
- Direct HTTP POST to the backing daemon with a 510-char body → succeeds, every time.
- A standalone
@modelcontextprotocol/sdkstdio client (same SDK version, same server binary) with 600- and 700-char bodies → succeeds, every time.
Only the Claude Code client path fails, and only intermittently. There is no fixed size threshold — it is connection-state dependent.
Reproduction notes
- Intermittent. Hard to force deterministically.
- Claude Code serializes tool calls to a given MCP server: parallel
tool_useblocks emitted in a single turn arrive at the server ~seconds apart (monotonically increasing JSON-RPC ids), so a "parallel burst" does not create concurrent wire pressure. - The failure correlates with
notifications/progressframes emitted by a streaming tool (a long-running search) interleaved with other calls — the dropped token in the log ("Fusing rankings") is such a streaming progress message.
Expected behavior
An unrecognized or late notifications/progress token is ignored; the stdio transport stays up; the in-flight tools/call completes with its arguments intact.
Environment
- Claude Code (latest)
- MCP server over stdio
@modelcontextprotocol/sdk1.29.0
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗