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
3 changes: 3 additions & 0 deletions src/mcp/server/streamable_http_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ async def _handle_stateful_request(self, scope: Scope, receive: Receive, send: S
if transport.idle_scope is not None and self.session_idle_timeout is not None:
transport.idle_scope.deadline = anyio.current_time() + self.session_idle_timeout # pragma: no cover
await transport.handle_request(scope, receive, send)
if transport.is_terminated:
self._server_instances.pop(request_mcp_session_id, None)
self._session_owners.pop(request_mcp_session_id, None)
return

if request_mcp_session_id is None:
Expand Down
30 changes: 30 additions & 0 deletions tests/server/test_streamable_http_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,36 @@ async def mock_receive():
assert not manager._server_instances, "No sessions should be tracked after the only session exits gracefully"


@pytest.mark.anyio
async def test_terminated_existing_session_is_removed_from_registry(
running_manager: tuple[StreamableHTTPSessionManager, Server],
):
manager, _app = running_manager
session_id = "terminated-session"
transport = AsyncMock()
transport.is_terminated = True
transport.idle_scope = None
manager._server_instances[session_id] = transport

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This regression test does not verify _session_owners cleanup because it never adds session_id to _session_owners before the request. Seed that registry first so the assertion can fail if owner removal breaks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/server/test_streamable_http_manager.py, line 316:

<comment>This regression test does not verify `_session_owners` cleanup because it never adds `session_id` to `_session_owners` before the request. Seed that registry first so the assertion can fail if owner removal breaks.</comment>

<file context>
@@ -304,6 +304,36 @@ async def mock_receive():
+    transport = AsyncMock()
+    transport.is_terminated = True
+    transport.idle_scope = None
+    manager._server_instances[session_id] = transport
+
+    scope = {
</file context>


scope = {
"type": "http",
"method": "DELETE",
"path": "/mcp",
"headers": [(MCP_SESSION_ID_HEADER.encode(), session_id.encode())],
}

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

async def mock_send(_message: Message):
pass

await manager.handle_request(scope, mock_receive, mock_send)

assert session_id not in manager._server_instances
assert session_id not in manager._session_owners


@pytest.mark.anyio
async def test_stateful_session_cleanup_on_exception(running_manager: tuple[StreamableHTTPSessionManager, Server]):
manager, _app = running_manager
Expand Down
Loading