[v1.x] fix: return HTTP 404 for unknown session IDs instead of 400 (#1945)
Some checks failed
Main branch checks / checks (push) Has been cancelled

Co-authored-by: Maxime <67350340+max-rousseau@users.noreply.github.com>
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
Luca Chang 2026-01-24 11:09:14 -08:00 • committed by GitHub
commit 3d9d34552a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 4 deletions

View file

@ -22,6 +22,7 @@ from mcp.server.streamable_http import (
StreamableHTTPServerTransport,
)
from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import INVALID_REQUEST, ErrorData, JSONRPCError
logger = logging.getLogger(__name__)
@ -276,10 +277,21 @@ class StreamableHTTPSessionManager:
# Handle the HTTP request and return the response
await http_transport.handle_request(scope, receive, send)
else: # pragma: no cover
# Invalid session ID
else:
# Unknown or expired session ID - return 404 per MCP spec
# TODO: Align error code once spec clarifies
# See: https://github.com/modelcontextprotocol/python-sdk/issues/1821
error_response = JSONRPCError(
jsonrpc="2.0",
id="server-error",
error=ErrorData(
code=INVALID_REQUEST,
message="Session not found",
),
)
response = Response(
"Bad Request: No valid session ID provided",
status_code=HTTPStatus.BAD_REQUEST,
content=error_response.model_dump_json(by_alias=True, exclude_none=True),
status_code=HTTPStatus.NOT_FOUND,
media_type="application/json",
)
await response(scope, receive, send)

View file

@ -1,5 +1,6 @@
"""Tests for StreamableHTTPSessionManager."""
import json
from typing import Any
from unittest.mock import AsyncMock, patch
@ -11,6 +12,7 @@ from mcp.server import streamable_http_manager
from mcp.server.lowlevel import Server
from mcp.server.streamable_http import MCP_SESSION_ID_HEADER, StreamableHTTPServerTransport
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.types import INVALID_REQUEST
@pytest.mark.anyio
@ -262,3 +264,52 @@ async def test_stateless_requests_memory_cleanup():
# Verify internal state is cleaned up
assert len(transport._request_streams) == 0, "Transport should have no active request streams"
@pytest.mark.anyio
async def test_unknown_session_id_returns_404():
"""Test that requests with unknown session IDs return HTTP 404 per MCP spec."""
app = Server("test-unknown-session")
manager = StreamableHTTPSessionManager(app=app)
async with manager.run():
sent_messages: list[Message] = []
response_body = b""
async def mock_send(message: Message):
nonlocal response_body
sent_messages.append(message)
if message["type"] == "http.response.body":
response_body += message.get("body", b"")
# Request with a non-existent session ID
scope = {
"type": "http",
"method": "POST",
"path": "/mcp",
"headers": [
(b"content-type", b"application/json"),
(b"accept", b"application/json, text/event-stream"),
(b"mcp-session-id", b"non-existent-session-id"),
],
}
async def mock_receive():
return {"type": "http.request", "body": b"{}", "more_body": False} # pragma: no cover
await manager.handle_request(scope, mock_receive, mock_send)
# Find the response start message
response_start = next(
(msg for msg in sent_messages if msg["type"] == "http.response.start"),
None,
)
assert response_start is not None, "Should have sent a response"
assert response_start["status"] == 404, "Should return HTTP 404 for unknown session ID"
# Verify JSON-RPC error format
error_data = json.loads(response_body)
assert error_data["jsonrpc"] == "2.0"
assert error_data["id"] == "server-error"
assert error_data["error"]["code"] == INVALID_REQUEST
assert error_data["error"]["message"] == "Session not found"