You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Client tool call hangs forever if server crashes or connection dies when using streamable-http #1577
If the server dies during a tool call before it sends a response the client will get stuck forever.
Example Code
# server.pyfrommcp.server.fastmcpimportFastMCPimportsysmcp=FastMCP("StatefulServer")
@mcp.tool()defgreet(name: str="World") ->str:
"""Greet someone by name."""sys.exit(0)
returnf"Hello, {name}!"if__name__=="__main__":
mcp.run(transport="streamable-http")
# client.pyimportasynciofrommcpimportClientSessionfrommcp.client.streamable_httpimportstreamablehttp_clientasyncdefmain():
# Connect to a streamable HTTP serverasyncwithstreamablehttp_client("http://localhost:8000/mcp") as (
read_stream,
write_stream,
_,
):
# Create a session using the client streamsasyncwithClientSession(read_stream, write_stream) assession:
# Initialize the connectionawaitsession.initialize()
res=awaitsession.call_tool("greet", {"name": "World"})
print(res)
if__name__=="__main__":
asyncio.run(main())
# Run server.py then client.py# client.py will hang forever in the tool call and never complete. I would expect it to throw an error since the connection died and it will never get a response.
"I encountered the same issue: exceptions at the underlying connection layer are not being broadcast to every waiting caller. Currently, the only workaround is to set a timeout for call tool operations to actively give up."
We hit this with mcp 1.28.1 against a hosted streamable-HTTP server that answers tools/call with an SSE response without id: fields and stays silent while a tool runs (some calls take several minutes). Details that may help whoever picks this up:
Paths in mcp/client/streamable_http.py (1.28.1) that leave ClientSession.send_request waiting forever:
_handle_sse_response: when the response stream ends or fails before the JSON-RPC response arrives (httpx.ReadTimeout, a peer reset, an intermediary dropping an idle connection, or a clean end of stream), the exception is only logged at DEBUG. If no SSE event carried an ID, there is no reconnect and nothing is delivered for the pending request.
_handle_reconnection: after MAX_RECONNECTION_ATTEMPTS it logs at DEBUG and returns, again without failing the request.
post_writer → handle_request_async: an exception from _handle_post_request (for example httpx.ReadTimeout while waiting for a JSON response) propagates into the task group and tears down the transport; the pending request is never answered.
The only mitigation today is a per-request read_timeout_seconds. It has to sit above the longest tool call, so a dropped stream still waits that long.
Minimal reproduction (no network, httpx.MockTransport; the tools/call response is SSE without event IDs that ends before the result):
On 1.28.1 this ends in the fail_afterTimeoutError.
What works for us: failing the pending request with a JSON-RPC error in those three places when the stream cannot be resumed, and keeping the existing resume behaviour when an event ID is present. The error is not retried, because the tool may already have run on the server. We carry this as a local patch pinned to 1.28.1 until it is fixed here.
Activity
gyang-xai commentedon Nov 5, 2025
I have a possible PR that fixes this here: #1578 for reference but not sure if this is the best solution.
Implement SEP-1577: Sampling With Tools
owenliang commentedon Feb 18, 2026
"I encountered the same issue: exceptions at the underlying connection layer are not being broadcast to every waiting caller. Currently, the only workaround is to set a timeout for call tool operations to actively give up."
astro-angelfish commentedon Jul 2, 2026
Reproduced on my side, just made my framework hangs forever. Maybe a keep-alive mechanism is required for streamable-http.
ossi-kronlof-fellowmind commentedon Oct 27, 2026
We hit this with mcp 1.28.1 against a hosted streamable-HTTP server that answers
tools/callwith an SSE response withoutid:fields and stays silent while a tool runs (some calls take several minutes). Details that may help whoever picks this up:Paths in
mcp/client/streamable_http.py(1.28.1) that leaveClientSession.send_requestwaiting forever:_handle_sse_response: when the response stream ends or fails before the JSON-RPC response arrives (httpx.ReadTimeout, a peer reset, an intermediary dropping an idle connection, or a clean end of stream), the exception is only logged at DEBUG. If no SSE event carried an ID, there is no reconnect and nothing is delivered for the pending request._handle_reconnection: afterMAX_RECONNECTION_ATTEMPTSit logs at DEBUG and returns, again without failing the request.post_writer→handle_request_async: an exception from_handle_post_request(for examplehttpx.ReadTimeoutwhile waiting for a JSON response) propagates into the task group and tears down the transport; the pending request is never answered.The only mitigation today is a per-request
read_timeout_seconds. It has to sit above the longest tool call, so a dropped stream still waits that long.Minimal reproduction (no network,
httpx.MockTransport; thetools/callresponse is SSE without event IDs that ends before the result):On 1.28.1 this ends in the
fail_afterTimeoutError.What works for us: failing the pending request with a JSON-RPC error in those three places when the stream cannot be resumed, and keeping the existing resume behaviour when an event ID is present. The error is not retried, because the tool may already have run on the server. We carry this as a local patch pinned to 1.28.1 until it is fixed here.