Skip to content

[BUG]: ASGI protocol violation in streamable_http: response emitted after completion causing ClosedResourceError #2671

@snmanikanta98

Description

@snmanikanta98

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:

  1. Completes an HTTP response
  2. Closes the underlying AnyIO memory stream
  3. Continues executing background tasks
  4. Attempts to:
    • write to a closed MemoryObjectSendStream
    • emit a second ASGI response

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 ClosedResourceError trigger response handling code
  • The runtime attempts to emit http.response.start even 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:

  1. POST /mcp
  2. Immediately close the connection or issue DELETE /mcp
  3. Server attempts to continue streaming
  4. ClosedResourceError followed 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 call writer.send() after the stream is closed

  • Gracefully ignore ClosedResourceError
    Do not emit responses after completion

  • Track whether http.response.start has 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.

Metadata

Metadata

Labels

SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate releasebugSomething isn't workingpythonPython / backend development (FastAPI)readyValidated, ready-to-work-on items

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions