Skip to content

AssertionError: Unexpected message while using middlewares #883

Open

Description

@toughnoah

Describe the bug
when I mount the mcp to starlette, and I use middleware to do some prechecks, I met this errors.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/app/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in call
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.12/site-packages/starlette/applications.py", line 113, in call
await self.middleware_stack(scope, receive, send)
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in call
raise exc
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in call
await self.app(scope, receive, _send)
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 182, in call
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/contextlib.py", line 158, in exit
self.gen.throw(value)
File "/app/.venv/lib/python3.12/site-packages/starlette/_utils.py", line 83, in collapse_excgroups
raise exc
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 185, in call
await response(scope, wrapped_receive, send)
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 223, in call
async for chunk in self.body_iterator:
File "/app/.venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 169, in body_stream
assert message["type"] == "http.response.body", f"Unexpected message: {message}"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: Unexpected message: {'type': 'http.response.start', 'status': 200, 'headers': [(b'content-length', b'0')]}

To Reproduce
Steps to reproduce the behavior:

  1. mount mcp to starlette
  2. use middlewares
  3. call tools
class CustomHeaderMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        return await call_next(request)


middleware = [
    Middleware(CustomHeaderMiddleware)
]

app = Starlette(middleware=middleware)
app.mount("/", mcp_server.sse_app())

def main():
    uvicorn.run(
        app="atlantis_mcp_server:app",
        host="0.0.0.0",
        port=8000,
        workers=1,
        log_level="info"
    )

__all__ = ["main"]

Expected behavior
Should not raise type error

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Activity

  1. Gelembjuk commented on Jun 5, 2025

    @Gelembjuk

    I have the same :(

  2. gothxx commented on Jun 10, 2025

    @gothxx

    I have the same error, dont known where to begin

  3. gothxx commented on Jun 10, 2025

    @gothxx

    maybe we should not use sse any more

  4. veonua commented on Jun 10, 2025

    @veonua

    @gothxx unfortunately, many clients support sse only

  5. sebastienmorand-loreal commented on Jun 23, 2025

    @sebastienmorand-loreal

    this is very simple to patch :

    Replace:
    assert message["type"] == "http.response.body"
    with
    assert message["type"] in ("http.response.body", "http.response.start")

    In BaseHTTPMiddleware line 163 of starlette/middleware/base.py

    Because of streaming using SSE, there is 2 kinds of message. Doing so manually no more trouble in my code

  6. Kludex commented on Jun 26, 2025

    @Kludex
    Member

    No. There's no issue in Starlette. The issue is in this repository.

    You are not supposed to send two http.response.start messages.

  7. Kludex commented on Jun 26, 2025

    @Kludex
    Member

    Please provide a code that I can copy/paste to test this, and I'd be happy to debug it.

  8. mclaramunt commented on Jul 2, 2025

    @mclaramunt

    I ran into the same issue and was able to solve it by rewriting my middleware as a pure ASGI middleware instead of relying on BaseHTTPMiddleware. https://www.starlette.io/middleware/#pure-asgi-middleware

  9. llamantino commented on Jul 22, 2025

    @llamantino

    @Kludex I hope this can help to reproduce the issue:

    from fastapi import FastAPI
    from fastmcp import FastMCP
    from starlette.middleware.base import BaseHTTPMiddleware
    import uvicorn
    
    mcp_server = FastMCP("test", stateless_http=True)
    
    class TestMW(BaseHTTPMiddleware):
        async def dispatch(self, request, call_next):
            return await call_next(request)
    
    app = FastAPI()
    app.add_middleware(TestMW)
    app.mount("/mcp", mcp_server.http_app(path="/sse", transport="sse"))
    
    if __name__ == "__main__":
        uvicorn.run(app, host="127.0.0.1", port=8000, http="h11")
    

    Then just run

    "curl http://127.0.0.1:8000/mcp/sse"

    and hit Ctrl+C to trigger the error.

  10. icyriverwolf commented on Jul 30, 2025

    @icyriverwolf

    me too
    AssertionError: Unexpected message: {'type': 'http.response.start', 'status': 200, 'headers': [(b'content-length', b'0')]}

  11. icyriverwolf commented on Jul 31, 2025

    @icyriverwolf

    No. There's no issue in Starlette. The issue is in this repository.不,Starlette 没有问题。问题出在这个仓库。

    You are not supposed to send two http.response.start messages.你不应该发送两个 http.response.start 消息。

    I encountered the same issue. When I add my own middleware, I get:
    AssertionError: Unexpected message: {'type': 'http.response.start', 'status': 200, 'headers': [(b'content-length', b'0')]}
    When I remove it, it works normally.
    I start my application using: uvicorn main:app --host 0.0.0.0 --port 7001 --log-level debug

  12. llamantino commented on Aug 16, 2025

    @llamantino

    As a temporary workaround, you can close the connection before Starlette raises the assert error by using a wrapper:

    def close_on_double_start(app):
        async def wrapped(scope, receive, send):
            start_sent = False
    
            async def check_send(message):
                nonlocal start_sent
                if message["type"] == "http.response.start":
                    if start_sent:
                        raise get_cancelled_exc_class()()
                    start_sent = True
                await send(message)
    
            await app(scope, receive, check_send)
    
        return wrapped
    

    then mount the SSE app with it:
    app.mount("/mcp", close_on_double_start(mcp_server.http_app(path="/sse", transport="sse")))

  13. added
    ready for workEnough information for someone to start working on
    P1Significant bug affecting many users, highly requested feature
    on Oct 3, 2025
  14. added a commit that references this issue on Feb 8, 2026
    cee78d3
  15. Ashutosh0x commented on Feb 8, 2026

    @Ashutosh0x

    Hi @toughnoah, I've submitted a fix for this in #2016. It addresses the double-send issue by ensuring the SSE handler returns correctly when used within Starlette/FastAPI middleware stacks.

  16. Kludex commented on Apr 5, 2026

    @Kludex
    Member

    @maxisbey we need to solve this for V2.

  17. Lcstyle commented on Jun 9, 2026

    @Lcstyle

    Confirmed on v1.27.2 + Starlette mount, workaround in production.

    Environment: Starlette/FastAPI app with mcp==1.27.2, server mounted at /mcp, EventSourceResponse-backed SSE.

    Trigger: any client closes the SSE stream (curl Ctrl+C, browser tab close, k8s probe disconnect).

    Stack trace head:

    RuntimeError: Expected ASGI message 'http.response.body', but got 'http.response.start'
      File ".../mcp/server/fastmcp/server.py", line 917, in sse_endpoint
      File ".../mcp/server/fastmcp/server.py", line 840, in handle_sse
    

    Downstream symptom that surprised us most: server crash → systemd Restart=on-failure brings the process back → the next client tool call lands on a fresh ServerSession before initialize completes → JSON-RPC -32602 "Received request before initialization was complete" surfaces to the client. The original ASGI error never reaches user-facing logs; only the cryptic init error does. Worth flagging since this issue body focuses on the ASGI error itself.

    Confirms @llamantino's analysis: sse_endpoint's return Response() after EventSourceResponse has already finalized the response causes Starlette's request_response wrapper to emit a duplicate http.response.start.

    Workaround we shipped: an outer ASGI middleware that drops a spurious http.response.start (and its orphaned body) when one was already emitted for the scope. It's idempotent against #2029's HandleSseAsgi fix — once upstream lands, the duplicate event stops arriving and the middleware becomes a silent no-op, so it can stay in indefinitely.

    Happy to help test or review if #2029 (or a fresh PR with the same HandleSseAsgi shape) gets traction.

    — Claude Code, via @Lcstyle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Significant bug affecting many users, highly requested featurebugSomething isn't workingready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions