Repository navigation
ClientDisconnect returns HTTP 500 #1648
Description
Activity
- changed the title
[-]ClientDisconnect returns HTTP 500 instead of 499[/-][+]ClientDisconnect returns HTTP 500[/+]on Nov 20, 2025 FanisPapakonstantinou commented
on Nov 20, 2025 AuthorMore actionsThe most relevant HTTP status to return would be the non-standard 499 used by Nginx. 499 is not standard HTTP and so this attempt PR fails in the pyright step. We could use 499 with type: ignore, change function signature to accept int | HTTPStatus or use standard HTTP 408.
- addedbugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working onP2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable feature
on Dec 2, 2025 I'd like to take a look at this. The fix should catch
ClientDisconnectbefore the broadexcept Exceptionhandler — log it as a warning (since it's a client-side event, not a server error), and return early without sending any response (the client has already disconnected). Will have a PR up shortly.- added a commit that references this issue
on Mar 12, 2026 - added a commit that references this issue
on May 5, 2026 I reproduced this against current
mainby simulatingClientDisconnectwhile reading a Streamable HTTP POST body. The narrow behavior I validated is to catch the disconnect aroundrequest.body(), log it as a client-side warning, and return without constructing a response because the peer is already gone. A regression test also confirms the session remains usable for a subsequent request. I have a focused implementation and can submit it if a maintainer decides an outside PR is useful here.AI assistance disclosure: I used Codex to help inspect, implement, and test this change; I reviewed the diff and test results myself.
Reacted by Konrad KomorowskiI opened #3414 with a minimal fix for this: it catches
ClientDisconnectbefore the generic exception path, avoids reporting disconnected clients as 500 server errors, and adds an ASGI-level regression test.Reacted by Konrad KomorowskiI’d like to work on this if the proposed behavior is still desired. My plan is to handle
starlette.requests.ClientDisconnectseparately in the Streamable HTTP POST path, avoid classifying a routine client disconnect as a server-side 500/error, and add a focused ASGI regression test that verifies the disconnect path does not emit an internal-error response.Disclosure: I use AI-assisted coding tools, and I will review, understand, and test the complete change before submitting it.
We hit this in production. During a period of elevated latency, clients started timing out and disconnecting while sending request bodies, and since each of those becomes a 500 they counted against our availability SLO as server faults. Our server-fault rate on POST went from about 0.05% to 1.6%, which against a 99.9% target is roughly 16x the error budget burn rate, and it paged. Essentially all of it was client disconnects rather than anything actually failing server-side.
Disclosure: I used AI tooling to investigate this. I hit the issue in production and reviewed this comment before posting.
I can take this. One note from the history: the earlier fix #3414 was closed by the auto-close bot for lack of assignment, not on review feedback, so the approach was never evaluated on the maintainer side. I reproduced the disconnect path on current main —
ClientDisconnectraised fromawait request.body()in_handle_post_requestfalls into the broadexcept Exceptionand comes out as an HTTP 500 with an error-level log.My approach: catch
ClientDisconnectaround the body read, log it as a warning (client-side event, not a server error), and return without writing a response since the peer is already gone. The generic handler stays untouched for real server errors. I will add an ASGI-level regression test simulating a mid-body disconnect, asserting no 500/internal-error response and that the session still serves a subsequent request.Disclosure: I use AI-assisted coding tools to help implement; I review, understand, and test the full change myself and take responsibility for the result. Please assign me #1648.
Initial Checks
Description
StreamableHTTPServerTransport._handle_post_requestinmcp/server/streamable_http.pyincorrectly handlesstarlette.requests.ClientDisconnectexceptions.Current behavior:
When This Occurs
ClientDisconnect happens during normal operations:
These are client-side events, not server failures.
Root Cause
File:
src/mcp/server/streamable_http.pyLine: ~490-500
The broad
except Exceptionhandler catchesClientDisconnectand returns 500:Example Code
Reproduction
Steps
1. Install MCP SDK:
python3 -m venv venv
source venv/bin/activate
pip install mcp
2. Create
minimal_mcp_server.pybased on the documentation:3. Create
test_client_disconnect.py:4. Run:
Terminal 1
python minimal_mcp_server.pyTerminal 2
python test_client_disconnect.py5. Observe the bug in Terminal 1:
Python & MCP Python SDK