Skip to content

fix(server): send JSON-RPC parse error when stdio transport fails to parse message#2755

Open
Oxygen56 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Oxygen56:fix/stdio-parse-error-no-response
Open

fix(server): send JSON-RPC parse error when stdio transport fails to parse message#2755
Oxygen56 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Oxygen56:fix/stdio-parse-error-no-response

Conversation

@Oxygen56
Copy link
Copy Markdown

@Oxygen56 Oxygen56 commented Jun 1, 2026

Description

When the stdio transport cannot parse an incoming JSON-RPC message (e.g. a deeply-nested ping request that exceeds pydantic's recursion limit during validate_json), stdin_readersends a rawException` through the read stream.

Previously, BaseSession._receive_loop() only forwarded this Exception to the application layer via _handle_incoming() without sending a JSON-RPC error response through the write stream. This left the client`'s request permanently pending until a client-side timeout.

Root Cause

The flow for deeply-nested JSON on stdio:

  1. stdin_reader calls jsonrpc_message_adapter.validate_json(line)ValidationError("recursion limit exceeded")
  2. The exception is caught and sent as a raw Exception through read_stream_writer.send(exc)
  3. BaseSession._receive_loop() receives the Exception, calls _handle_incoming(exc), but never sends a JSON-RPC response
  4. Client waits forever for a response that will never arrive

Fix

Catch the transport-level Exception in BaseSession._receive_loop() and send a proper JSON-RPC parse error (code: -32700) with id: null through the write stream, matching JSON-RPC spec §5 and the existing Streamable HTTP transport behaviour.

Before (broken):

Client sends deeply-nested JSON → server logs error internally → no response → client hangs

After (fixed):

Client sends deeply-nested JSON → server sends {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}} → client can handle error

Changes

  • src/mcp/shared/session.py: Added PARSE_ERROR import; added JSON-RPC error response emission in the isinstance(message, Exception) branch of _receive_loop()

The _handle_incoming(message) call is preserved so the application layer can still observe transport-level failures.

Fixes #2751

…parse message

When the stdio transport cannot parse an incoming JSON-RPC message
(e.g. deeply-nested JSON exceeding the recursion limit), the stdin_reader
sends a raw Exception through the read_stream. Previously, the session
only forwarded this Exception to the application layer via _handle_incoming,
never sending a JSON-RPC error response. The client request would hang
indefinitely waiting for a response that never arrived.

This aligns stdio behaviour with the Streamable HTTP transport, which
already returns a proper JSON-RPC parse error (code -32700) for
unparseable messages.

Fixes modelcontextprotocol#2751

Signed-off-by: Willow Lopez <100782273+Oxygen56@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deep JSON-RPC request over stdio logs a validation error and leaves the original request pending

1 participant