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
Ninguém assumiu esta issue ainda.
- Linguagem predominante
- Python
- Estrelas
- 24.3k
- Forks
- 4k
- Merge médio
- 1d 1h
- PRs com merge (30d)
- 31
Descrição
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.
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Direção de pesquisa
Comece reproduzindo as duas requisições curl descritas na issue e, em seguida, rastreie os pontos de entrada do servidor HTTP streamable _handle_get_request, _check_accept_headers e _validate_session. Verifique o comportamento do GET antes da sessão e os casos do cabeçalho Accept; depois, adicione ou atualize a cobertura para que um GET não suportado retorne 405 com um cabeçalho Allow, preservando o tratamento válido de SSE.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- python
- Domínio
- api, backend, networking
- Tipo de issue
- Bug
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Status de atividade
- Pouca atividade
- Clareza
- Razoavelmente clara
- Facilidade para iniciantes
- 58/100