Repository navigation
AssertionError: Unexpected message while using middlewares #883
Description
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:
- mount mcp to starlette
- use middlewares
- 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
Gelembjuk commentedon Jun 5, 2025
I have the same :(
gothxx commentedon Jun 10, 2025
I have the same error, dont known where to begin
gothxx commentedon Jun 10, 2025
maybe we should not use sse any more
veonua commentedon Jun 10, 2025
@gothxx unfortunately, many clients support sse only
sebastienmorand-loreal commentedon Jun 23, 2025
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
Kludex commentedon Jun 26, 2025
No. There's no issue in Starlette. The issue is in this repository.
You are not supposed to send two
http.response.startmessages.Kludex commentedon Jun 26, 2025
Please provide a code that I can copy/paste to test this, and I'd be happy to debug it.
mclaramunt commentedon Jul 2, 2025
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
llamantino commentedon Jul 22, 2025
@Kludex I hope this can help to reproduce the issue:
Then just run
"curl http://127.0.0.1:8000/mcp/sse"
and hit Ctrl+C to trigger the error.
icyriverwolf commentedon Jul 30, 2025
me too
AssertionError: Unexpected message: {'type': 'http.response.start', 'status': 200, 'headers': [(b'content-length', b'0')]}
icyriverwolf commentedon Jul 31, 2025
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
llamantino commentedon Aug 16, 2025
As a temporary workaround, you can close the connection before Starlette raises the assert error by using a wrapper:
then mount the SSE app with it:
app.mount("/mcp", close_on_double_start(mcp_server.http_app(path="/sse", transport="sse")))Fix modelcontextprotocol#883: AssertionError with Starlette middlewar…
Ashutosh0x commentedon Feb 8, 2026
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.
fix: use ASGI callable for SSE endpoint to avoid BaseHTTPMiddleware A…
style: format and lint fix for modelcontextprotocol#883
Kludex commentedon Apr 5, 2026
@maxisbey we need to solve this for V2.
Lcstyle commentedon Jun 9, 2026
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:
Downstream symptom that surprised us most: server crash →
systemd Restart=on-failurebrings the process back → the next client tool call lands on a freshServerSessionbeforeinitializecompletes → 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'sreturn Response()afterEventSourceResponsehas already finalized the response causes Starlette'srequest_responsewrapper to emit a duplicatehttp.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'sHandleSseAsgifix — 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
HandleSseAsgishape) gets traction.— Claude Code, via @Lcstyle