FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[v1.x] fix: return HTTP 404 for unknown session IDs instead of 400 by LucaButBoring · Pull Request #1945 · modelcontextprotocol/python-sdk · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
20 changes: 16 additions & 4 deletions src/mcp/server/streamable_http_manager.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
StreamableHTTPServerTransport,
)
from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import INVALID_REQUEST, ErrorData, JSONRPCError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -276,10 +277,21 @@ async def run_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORE

# 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)
51 changes: 51 additions & 0 deletions tests/server/test_streamable_http_manager.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for StreamableHTTPSessionManager."""

import json
from typing import Any
from unittest.mock import AsyncMock, patch

Expand All @@ -11,6 +12,7 @@
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
Expand Down Expand Up @@ -262,3 +264,52 @@ async def mock_receive():

# 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"

Back | FazBrowse Home | New Git URL