FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(server): register SSE GET as raw ASGI so BaseHTTPMiddleware does not see a second response start by jwilson411 · Pull Request #3381 · modelcontextprotocol/python-sdk · GitHub

fix(server): register SSE GET as raw ASGI so BaseHTTPMiddleware does not see a second response start - #3381

Closed
jwilson411 wants to merge 1 commit into
modelcontextprotocol:mainfrom
jwilson411:oss/UP-12/20260824222122
Closed

fix(server): register SSE GET as raw ASGI so BaseHTTPMiddleware does not see a second response start#3381
jwilson411 wants to merge 1 commit into
modelcontextprotocol:mainfrom
jwilson411:oss/UP-12/20260824222122

Conversation

Copy link
Copy Markdown

Fixes #883.

What I hit

I reproduced this on current main (56af447) by mounting MCPServer.sse_app(host="0.0.0.0") under a Starlette app that uses a no-op BaseHTTPMiddleware, opening /sse, reading the first endpoint event, then closing the client. Same AssertionError:

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

Stashing only src/mcp/server/mcpserver/server.py after this change makes test_sse_app_under_base_http_middleware_survives_client_disconnect_without_second_response fail with that same error.

Why

sse_app() registered the unauth SSE GET as a Starlette function endpoint. Starlette wraps those in request_response. EventSourceResponse already finalized the stream; the wrapper then sent a second http.response.start (empty Response()). BaseHTTPMiddleware asserts it only sees http.response.body after the first start. Kludex already called this on the issue: the SDK must not send two starts.

The auth path already passed a class instance (RequireAuthMiddleware) to Route, which Starlette treats as raw ASGI. The unauth path did not.

Fix

  • Register the SSE GET as a raw ASGI app (_RawASGIEndpoint) instead of a function endpoint.
  • Stop returning Response() after connect_sse has already sent the complete response.
  • Update the SseServerTransport module example so it no longer tells users to return a second Response().

No new public API. Route shape is unchanged (one Route at /sse, one Mount at /messages).

Tests

  • test_sse_app_under_base_http_middleware_survives_client_disconnect_without_second_response — in-process StreamingASGITransport, no-op BaseHTTPMiddleware, asserts exactly one http.response.start after client disconnect.

uv run --frozen pytest tests/shared/test_sse.py tests/server/test_sse_security.py tests/interaction/transports/test_sse.py tests/server/mcpserver/test_server.py → 227 passed.
uv run --frozen pyright on the three changed files → 0 errors.

Notes

#2016 / #2029 tried a similar ASGI-callable approach and were closed unmerged. #1222 is still an open draft. This is a fresh patch against current main, not a rebase of those.

AI-assisted implementation (Claude Code). I reproduced the failure on this tree, reviewed the diff, and reran the SSE / MCPServer tests before opening this.

I have not asked to be assigned. If the auto-close bot parks this until a maintainer wants an outside PR, that is understood.

…not see a second response start

Starlette wraps function endpoints in request_response. After EventSourceResponse
has already finalized the SSE stream, that wrapper sent a second
http.response.start (empty Response), which BaseHTTPMiddleware asserts against
on client disconnect (modelcontextprotocol#883).
github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Aug 24, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. This repository only keeps pull requests open when they're linked to an issue that a maintainer has assigned to the author — CONTRIBUTING.md explains why and how we work. This PR has been closed for now because you aren't currently assigned to #883.

If a maintainer would like this change as a PR from you, they'll assign you to #883 and this PR will reopen automatically — there's nothing more you need to do. (If you opened the issue, this PR already shows up on its timeline.)

There's no need to open a new PR — this one will be reopened. While it's closed, please push any updates as new commits rather than force-pushing, since GitHub can't reopen a PR whose branch has been rewritten.

Maintainers: reopening this PR, removing the missing-issue-link label, or adding bypass-issue-check bypasses the check.

github-actions Bot closed this Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AssertionError: Unexpected message while using middlewares

1 participant


Back | FazBrowse Home | New Git URL