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