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

未关闭
#3,102 5 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

bug needs confirmation P2 v1 v2
主要语言
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-stream in 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_headers still 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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 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

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。