modelcontextprotocol / modelcontextprotocol/python-sdk
Streamable HTTP server never returns the spec-mandated 405 for GET requests it won't serve as SSE (406/400 instead) — breaks client SSE-probe fallback
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 24.3k
- 分支
- 4k
- 平均合併
- 1 天 1 小時
- 30 天內合併 PR
- 31
描述
Summary
The streamable-HTTP server transport never answers a GET it won't serve with 405 Method Not Allowed, even though the spec makes 405 the required signal:
The server MUST either return
Content-Type: text/event-streamin response to this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server does not offer an SSE stream at this endpoint.
— Streamable HTTP § Listening for Messages from the Server
Instead, a GET that can't be served yields:
Pre-session GET /mcp with… |
Server response |
|---|---|
Accept: */*, Accept: application/json, or no Accept |
406 Not Acceptable (_handle_get_request → _check_accept_headers, which does literal prefix matching so */* is not honored) |
Accept: text/event-stream (stateful server, no mcp-session-id) |
400 Bad Request: Missing session ID (_validate_session) |
So in stateful mode there is no request shape for which a pre-session GET returns 405. (405 is used elsewhere: DELETE without a session and unsupported methods.)
Why it matters — real interop break
Several client transports probe with exactly this GET before initialize to ask "do you offer a standalone SSE stream?", and treat only 405 as the graceful "no SSE — fall through to POST" signal (e.g. the TypeScript SDK's _startOrAuthSse explicitly special-cases 405). Any other status is a transport error, so against a stock python-SDK server the connection aborts before initialize is ever sent.
We hit this in production between two independently-built agents: the client authenticated successfully, then its GET probe got 406 (its relay sent Accept: */*), the transport threw, and the handshake never reached initialize — surfacing as "server doesn't respond to MCP protocol" while the server looked perfectly healthy to every python-SDK client (which only GETs after initialize, with a session id). We've deployed a workaround (a small ASGI shim returning 405 + Allow: POST for session-less GETs on the MCP path), but every stock python-SDK deployment presumably reproduces this.
Repro (mcp 1.28.1, stateful StreamableHTTP server)
curl -i -X GET http://localhost:8000/mcp -H 'Accept: */*' # → 406, expected 405
curl -i -X GET http://localhost:8000/mcp -H 'Accept: text/event-stream' # → 400, expected 405 (no session yet)
Suggested behavior
For a GET the server cannot serve as an SSE stream, respond 405 with an Allow header per the spec quote above — at minimum for the pre-session case (no mcp-session-id), where returning 400 makes the spec'd client probe impossible to satisfy. Separately (or as part of this), _check_accept_headers honoring */* / text/* per RFC 9110 §12.5.1 would remove the 406 arm for wildcard clients.
Related
- #2349 covers the POST-side strict dual-Accept requirement; this issue is about the GET/405 contract, which is distinct.
- #1641 raised wildcard-Accept non-compliance and is closed, but on 1.28.1
_check_accept_headersstill does literal prefix matching, and the GET path 406s wildcard clients.
Happy to provide full header traces or a PR if the maintainers agree on the intended shape.
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
先重現 issue 中描述的兩個 curl 請求,接著追蹤 streamable HTTP 伺服器的進入點 _handle_get_request、_check_accept_headers 和 _validate_session。驗證工作階段建立前的 GET 行為以及 Accept 標頭的各種情況,然後新增或更新涵蓋範圍,使不受支援的 GET 回傳帶有 Allow 標頭的 405,同時保留有效的 SSE 處理。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- api, backend, networking
- Issue 類型
- 缺陷
- 難度
- 3/5
- 預估耗時
- 1-2 天
- 活躍度
- 冷清
- 描述清晰度
- 基本清楚
- 新手友好度
- 58/100