Skip to content
Open
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
5 changes: 4 additions & 1 deletion src/mcp/server/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,10 @@ async def terminate(self) -> None:
"""

self._terminated = True
logger.info(f"Terminating session: {self.mcp_session_id}")
if self.mcp_session_id:
logger.info(f"Terminating session: {self.mcp_session_id}")
else:
logger.debug("Stateless request completed, cleaning up transport")

# We need a copy of the keys to avoid modification during iteration
request_stream_keys = list(self._request_streams.keys())
Expand Down
42 changes: 42 additions & 0 deletions tests/server/test_streamable_http_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,48 @@ async def mock_receive():
assert len(transport._request_streams) == 0, "Transport should have no active request streams"


@pytest.mark.anyio
async def test_stateless_termination_logs_debug_not_info(caplog: pytest.LogCaptureFixture):
"""Stateless mode termination should use DEBUG logs and avoid INFO-level "None" session text."""
app = Server("test-stateless-log-level")
manager = StreamableHTTPSessionManager(app=app, stateless=True)

async with manager.run():
app.run = AsyncMock(return_value=None)

async def mock_send(message: Message):
del message

scope = {
"type": "http",
"method": "POST",
"path": "/mcp",
"headers": [
(b"content-type", b"application/json"),
(b"accept", b"application/json, text/event-stream"),
],
}

async def mock_receive():
return {
"type": "http.request",
"body": b"",
"more_body": False,
}

with caplog.at_level(logging.DEBUG, logger="mcp.server.streamable_http"):
await manager.handle_request(scope, mock_receive, mock_send)

streamable_http_messages = [
record.getMessage()
for record in caplog.records
if record.name == "mcp.server.streamable_http"
]

assert "Stateless request completed, cleaning up transport" in streamable_http_messages
assert "Terminating session: None" not in streamable_http_messages


@pytest.mark.anyio
async def test_unknown_session_id_returns_404(caplog: pytest.LogCaptureFixture):
"""Test that requests with unknown session IDs return HTTP 404 per MCP spec."""
Expand Down
Loading