Skip to content

ClientDisconnect returns HTTP 500 #1648

@FanisPapakonstantinou

Description

@FanisPapakonstantinou

Initial Checks

Description

StreamableHTTPServerTransport._handle_post_request in mcp/server/streamable_http.py incorrectly handles starlette.requests.ClientDisconnect exceptions.

Current behavior:

  • Returns HTTP 500 (Internal Server Error)
  • Logs as ERROR with full traceback
  • Triggers production 5XX alerts

When This Occurs

ClientDisconnect happens during normal operations:

  • Network timeouts
  • User cancels request
  • Load balancer timeouts
  • Mobile client network interruptions

These are client-side events, not server failures.

Root Cause

File: src/mcp/server/streamable_http.py
Line: ~490-500

The broad except Exception handler catches ClientDisconnect and returns 500:

except Exception as err:  # pragma: no cover
    logger.exception("Error handling POST request")  # ❌ Logs as ERROR
    response = self._create_error_response(
        f"Error handling POST request: {err}",
        HTTPStatus.INTERNAL_SERVER_ERROR,  # ❌ Returns 500
        INTERNAL_ERROR,
    )
    await response(scope, receive, send)

Example Code

## Reproduction

### Steps

**1. Install MCP SDK:**

python3 -m venv venv
source venv/bin/activate
pip install mcp


**2. Create `minimal_mcp_server.py` based on the documentation:**

#!/usr/bin/env python3
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Bug Demo", json_response=True)

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

if __name__ == "__main__":
    mcp.run(transport="streamable-http")


**3. Create `test_client_disconnect.py`:**

#!/usr/bin/env python3
import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", 8000))

# Send headers claiming 100KB body
headers = (
    b"POST /mcp HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Content-Type: application/json\r\n"
    b"Content-Length: 100000\r\n"
    b"Accept: application/json, text/event-stream\r\n"
    b"\r\n"
)
sock.send(headers)

# Send partial body then disconnect
sock.send(b'{"jsonrpc": "2.0", "method": "initialize", "params": {')
time.sleep(0.05)
sock.close()

print("✓ Client disconnect simulated")


**4. Run:**

# Terminal 1
python minimal_mcp_server.py

# Terminal 2
python test_client_disconnect.py


**5. Observe the bug in Terminal 1:**

Error handling POST request
Traceback (most recent call last):
  File ".../mcp/server/streamable_http.py", line 351, in _handle_post_request
    body = await request.body()
           ^^^^^^^^^^^^^^^^^^^^
  File ".../starlette/requests.py", line 243, in body
    async for chunk in self.stream():
  File ".../starlette/requests.py", line 237, in stream
    raise ClientDisconnect()
starlette.requests.ClientDisconnect

Python & MCP Python SDK

Python 3.12.9, MCP Python SDK v1.21.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions