Repository navigation
Streamable HTTP client hangs on a null-id JSON-RPC error in a 200 JSON response #3639
Description
Activity
- addedv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Oct 3, 2026 Hi team,
I investigated this issue against current
main(2118f14f).Looking at
_handle_json_responseinsrc/mcp/client/streamable_http.py:async def _handle_json_response( self, response: httpx2.Response, read_stream_writer: StreamWriter, *, request_id: RequestId, ) -> None: content = await response.aread() message = jsonrpc_message_adapter.validate_json(content, by_name=False) session_message = SessionMessage(message) await read_stream_writer.send(session_message)
The function receives
request_id, but does not apply it when the returnedJSONRPCError(or response) hasid: None. When this happens,_resolve_pendinginjsonrpc_dispatcher.pycannot correlate the response, logs"dropping response for unknown/late request id None", and the calling task hangs awaiting the pending future.The SSE path (
_handle_sse_event, line 193) and the non-2xx branch (_handle_post_request, line 410) already correlate null-id errors with the original request ID.I'm putting together a targeted PR that:
- Updates
_handle_json_responseto correlatemessage.id = request_idwhenmessage.id is None. - Adds a regression test in
tests/client/test_streamable_http.pyverifying that a 200 response with{"jsonrpc": "2.0", "id": null, "error": {...}}cleanly raisesMcpErrorrather than hanging.
Opening a PR shortly with the reproduction test and fix.
Reacted by Hyeonsang Cho- Updates
I hit the same hang on current
main: a200 application/jsonbody with a JSON-RPC error whoseidisnullis forwarded unchanged from_handle_json_response, so the in-flight waiter never resolves. The non-2xx branch already remapsid: nullto the outbound request id (~L408–411); the 200 JSON path does not.Proposed fix: in
_handle_json_response, when the parsed message is aJSONRPCErrorwith a null id, rewriteidto therequest_idargument before sending onread_stream_writer. Add a regression test where a Streamable HTTP stub returns that 200 body and assertlist_tools()(or any request) fails fast with the remapped error instead of hanging.If that direction looks right, I'd like to be assigned so I can open the PR under the external-contributor rule.
Initial Checks
Release line
2.x (current stable)
Description
On current
main(2118f14f), when a server answers a request POST with a200 application/jsonbody holding a JSON-RPC error with"id": null, the streamable HTTP client never resolves the call.session.list_tools()(or any request) waits forever, or until a read timeout if one is set.JSON-RPC 2.0 allows
id: nullon an error when the server could not determine the request id. Gateways and non-SDK servers can send one with status 200._handle_json_responseinsrc/mcp/client/streamable_http.pyforwards the parsed message unchanged. The dispatcher then drops it as a response to an unknown id (jsonrpc_dispatcher.py, "unknown/late request id").The other two response paths in the same transport already handle this:
_handle_sse_event): replaces a response's id with the original request id._handle_post_request): rebuilds the error under the request's id. The comment there says: "The server may have setid: null(request rejected before its id was parsed); use this request's id so correlation works."So the same error body resolves the call over SSE or with a 4xx status, but hangs with a 200.
Expected: the call raises
MCPErrorwith the server's error, the same as the SSE and non-2xx paths.Proposed fix: in
_handle_json_response, when the parsed message is aJSONRPCError, set its id to the POST's request id. A 2xx JSON body answers exactly that POST, so nothing else can be waiting on it. That is a 4-line change plus a regression test next to the existing non-2xx null-id test intests/client/test_notification_response.py.I'd like to fix this and have a branch ready: https://github.com/hyeonsang010716/python-sdk/tree/fix/streamable-http-json-null-id
I found and prepared this with AI assistance. I've reviewed the change, checked the repro below fails on
mainand passes with the fix, and can explain it.Example Code
Output on
main:Output with the fix:
Python & MCP Python SDK