Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/mcp/server/streamable_http_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ async def run_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORE
read_stream, write_stream = streams
task_status.started()
try:
# Use a cancel scope for idle timeout when the
# Use a cancel scope for idle timeout \u2014 when the
# deadline passes the scope cancels app.run() and
# execution continues after the ``with`` block.
# Incoming requests push the deadline forward.
Expand Down Expand Up @@ -272,6 +272,7 @@ async def run_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORE
# 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
logger.warning("Rejected request with unknown or expired session ID: %s", request_mcp_session_id)
error_response = JSONRPCError(
jsonrpc="2.0",
id=None,
Expand Down
11 changes: 9 additions & 2 deletions tests/server/test_streamable_http_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for StreamableHTTPSessionManager."""

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

Expand Down Expand Up @@ -269,7 +270,7 @@ async def mock_receive():


@pytest.mark.anyio
async def test_unknown_session_id_returns_404():
async def test_unknown_session_id_returns_404(caplog: pytest.LogCaptureFixture):
"""Test that requests with unknown session IDs return HTTP 404 per MCP spec."""
app = Server("test-unknown-session")
manager = StreamableHTTPSessionManager(app=app)
Expand Down Expand Up @@ -299,7 +300,8 @@ async def mock_send(message: Message):
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)
with caplog.at_level(logging.WARNING, logger="mcp.server.streamable_http_manager"):
await manager.handle_request(scope, mock_receive, mock_send)

# Find the response start message
response_start = next(
Expand All @@ -316,6 +318,11 @@ async def mock_receive():
assert error_data["error"]["code"] == INVALID_REQUEST
assert error_data["error"]["message"] == "Session not found"

# Verify warning was logged with the session ID
assert any("non-existent-session-id" in record.message for record in caplog.records), (
"Should log a warning with the unknown session ID"
)


@pytest.mark.anyio
async def test_e2e_streamable_http_server_cleanup():
Expand Down