-
Notifications
You must be signed in to change notification settings - Fork 593
Description
Summary
The MCP agent runtime violates the ASGI response lifecycle by attempting to emit response messages after the response has already completed.
This results in repeated runtime failures such as:
anyio.ClosedResourceError
RuntimeError: Unexpected ASGI message 'http.response.start' sent, after response already completed
The issue occurs under normal usage when a client disconnects, a session is deleted, or an MCP stream is terminated while background writers are still active.
This is a server-side protocol violation inside the MCP agent runtime (FastMCP).
The MCP Gateway only forwards requests and does not participate in response streaming or ASGI lifecycle management.
.
Environment
- MCP agent runtime (FastMCP)
- Transport:
streamable-http - Python: 3.10
- ASGI server: Uvicorn
- Concurrency library: AnyIO
- Reproduces across environments (DEV / UAT)
Versions
- FastMCP: 2.14.4
- image: 1.0.0-BETA2
- Transport: streamable-http
- Python: 3.10.x
- Uvicorn: 0.23+ (ASGI strict enforcement observed)
- AnyIO: 4.x
- FastAPI / Starlette: (as bundled with FastMCP 2.14.4)
Observed Behavior
Under certain timing conditions (client disconnect, DELETE /mcp, or stream termination), the agent:
- Completes an HTTP response
- Closes the underlying AnyIO memory stream
- Continues executing background tasks
- Attempts to:
- write to a closed
MemoryObjectSendStream - emit a second ASGI response
- write to a closed
This produces errors such as:
anyio.ClosedResourceError
RuntimeError: Unexpected ASGI message 'http.response.start' sent, after response already completed
The agent continues running, but logs are flooded with protocol errors.
Why This Is Not a Gateway Issue
- The same MCP Gateway works correctly with other agents
- The issue reproduces when calling the agent directly (bypassing the gateway)
- Failures occur after the HTTP response has completed inside the agent runtime
- The gateway does not emit ASGI messages or manage AnyIO streams
This confirms the fault lies entirely within the agent's ASGI / streaming lifecycle handling.
Expected Behavior
Once an HTTP response has completed:
- No further ASGI messages should be emitted
- Background writers must stop immediately
- Writers must not attempt to send messages after:
- client disconnect
- session deletion
- stream closure
- Closed streams should be treated as a terminal condition, not an exception that triggers further response handling
Stack Trace (Representative)
anyio.ClosedResourceError
File ".../mcp/server/streamable_http.py", line 518, in _handle_post_request
await writer.send(session_message)
RuntimeError: Unexpected ASGI message 'http.response.start' sent, after response already completed
File ".../uvicorn/protocols/http/h11_impl.py", line 520, in send
raise RuntimeError(msg % message_type)
Root Cause Analysis
This is a race condition and lifecycle management bug in the MCP agent runtime.
:
- Writers continue sending messages after the stream has been closed
- Exceptions from
ClosedResourceErrortrigger response handling code - The runtime attempts to emit
http.response.starteven though the response has already ended
This violates the ASGI contract and is explicitly rejected by Uvicorn.
Affected Code Paths
mcp/server/streamable_http.py
Notable problematic areas:
- Writing to
writer.send()without checking whether the stream is closed - Exception handlers that emit a response even when one has already completed
- Background tasks not cancelled when a session is terminated
Minimal Reproducer
import anyio
from fastapi import FastAPI
from starlette.responses import StreamingResponse
app = FastAPI()
@app.post("/mcp")
async def broken_stream():
async def gen():
yield "start\n"
await anyio.sleep(0.1)
yield "end\n"
return StreamingResponse(gen(), media_type="text/plain")Client behavior:
POST /mcp- Immediately close the connection or issue
DELETE /mcp - Server attempts to continue streaming
ClosedResourceErrorfollowed by ASGI runtime error
This reproduces the same failure pattern observed in production logs.
Why Reinstalling the Agent Appears to Help (Temporarily)
Reinstalling the agent:
- Clears in-flight sessions
- Resets memory streams
- Reduces concurrency temporarily
This does not fix the underlying code path.
As soon as similar timing conditions occur, the error returns.
This confirms the issue is not configuration-related.
Suggested Fixes (High Level)
-
Guard all writer sends
Do not callwriter.send()after the stream is closed -
Gracefully ignore
ClosedResourceError
Do not emit responses after completion -
Track whether
http.response.starthas already been sent
Never attempt to send another response afterward -
Cancel background tasks on session termination
When a session ends, all associated writers and producers must stop immediately
Impact
- Continuous error logs under normal client behavior
- ASGI protocol violations
- Difficult debugging and false attribution to gateways or network layers
- Potential instability under load
Conclusion
This is a server-side agent runtime bug caused by improper stream lifecycle handling and response management.
The issue is reproducible, protocol-level, and independent of gateways or deployment configuration.