From 666293979c58155a8c92e3797c24b79e9c8c7321 Mon Sep 17 00:00:00 2001 From: RameshReddy Adutla Date: Wed, 4 Mar 2026 21:18:53 +0000 Subject: [PATCH 1/3] Add warning log when rejecting request with unknown/expired session ID The else branch in _handle_stateful_request() silently returns 404 when a request has an unknown or expired session ID. The other two branches (existing session, new session) both log at debug/info level. Add a logger.warning() call so operators can diagnose why client connections are being rejected, e.g. after a server restart when clients send stale session IDs. Fixes modelcontextprotocol#2204 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/mcp/server/streamable_http_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mcp/server/streamable_http_manager.py b/src/mcp/server/streamable_http_manager.py index 50bcd5e79..50551a30e 100644 --- a/src/mcp/server/streamable_http_manager.py +++ b/src/mcp/server/streamable_http_manager.py @@ -272,6 +272,10 @@ 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( + f"Rejected request with unknown or expired session ID: " + f"{request_mcp_session_id}" + ) error_response = JSONRPCError( jsonrpc="2.0", id=None, From 6f99c3d05b3fbbca518178b69845bce55e69c720 Mon Sep 17 00:00:00 2001 From: RameshReddy Adutla Date: Wed, 4 Mar 2026 21:26:24 +0000 Subject: [PATCH 2/3] Fix ruff format: single-line warning log message Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/mcp/server/streamable_http_manager.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mcp/server/streamable_http_manager.py b/src/mcp/server/streamable_http_manager.py index 50551a30e..d822f791e 100644 --- a/src/mcp/server/streamable_http_manager.py +++ b/src/mcp/server/streamable_http_manager.py @@ -272,10 +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( - f"Rejected request with unknown or expired session ID: " - f"{request_mcp_session_id}" - ) + logger.warning(f"Rejected request with unknown or expired session ID: {request_mcp_session_id}") error_response = JSONRPCError( jsonrpc="2.0", id=None, From 3041fd0b5c855e6137bea5101f9be366fdea6fa1 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:50:33 +0000 Subject: [PATCH 3/3] Use info level and truncate client-controlled session ID - warning -> info: this is normal post-restart operation per spec, matches session lifecycle logs at :220 and :247 - truncate to 64 chars: unlike other session-ID logs in this file, this value is client-controlled input - add caplog assertion to existing 404 test Reported-by: Johnathan Oneal Github-Issue: #2204 --- src/mcp/server/streamable_http_manager.py | 2 +- tests/server/test_streamable_http_manager.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mcp/server/streamable_http_manager.py b/src/mcp/server/streamable_http_manager.py index d822f791e..c25314eab 100644 --- a/src/mcp/server/streamable_http_manager.py +++ b/src/mcp/server/streamable_http_manager.py @@ -272,7 +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(f"Rejected request with unknown or expired session ID: {request_mcp_session_id}") + logger.info(f"Rejected request with unknown or expired session ID: {request_mcp_session_id[:64]}") error_response = JSONRPCError( jsonrpc="2.0", id=None, diff --git a/tests/server/test_streamable_http_manager.py b/tests/server/test_streamable_http_manager.py index 54a898cc5..47cfbf14a 100644 --- a/tests/server/test_streamable_http_manager.py +++ b/tests/server/test_streamable_http_manager.py @@ -1,6 +1,7 @@ """Tests for StreamableHTTPSessionManager.""" import json +import logging from typing import Any from unittest.mock import AsyncMock, patch @@ -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) @@ -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.INFO): + await manager.handle_request(scope, mock_receive, mock_send) # Find the response start message response_start = next( @@ -315,6 +317,7 @@ async def mock_receive(): assert error_data["id"] is None assert error_data["error"]["code"] == INVALID_REQUEST assert error_data["error"]["message"] == "Session not found" + assert "Rejected request with unknown or expired session ID: non-existent-session-id" in caplog.text @pytest.mark.anyio