Skip to content

Commit 65356a1

Browse files
fix(streamable-http): downgrade stateless 'Terminating session: None' log
In stateless mode every request created a transport with mcp_session_id=None and terminated it on completion, producing 'INFO: Terminating session: None' on every request. The repeated noise made real session terminations hard to find and confused users into thinking their connection was dropping. Branch on mcp_session_id in terminate(): keep the existing INFO log for stateful session terminations, and switch the stateless path to a DEBUG log with a clearer message ("Stateless request completed, cleaning up transport"). Adds two caplog tests covering both branches. Closes #2329.
1 parent e8e6484 commit 65356a1

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/mcp/server/streamable_http.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,10 @@ async def terminate(self) -> None:
769769
"""
770770

771771
self._terminated = True
772-
logger.info(f"Terminating session: {self.mcp_session_id}")
772+
if self.mcp_session_id is not None:
773+
logger.info(f"Terminating session: {self.mcp_session_id}")
774+
else:
775+
logger.debug("Stateless request completed, cleaning up transport")
773776

774777
# We need a copy of the keys to avoid modification during iteration
775778
request_stream_keys = list(self._request_streams.keys())

tests/shared/test_streamable_http.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations as _annotations
77

88
import json
9+
import logging
910
import multiprocessing
1011
import socket
1112
import time
@@ -767,6 +768,43 @@ def test_streamable_http_transport_init_validation():
767768
StreamableHTTPServerTransport(mcp_session_id="test\n")
768769

769770

771+
@pytest.mark.anyio
772+
async def test_terminate_stateless_log_is_debug(caplog: pytest.LogCaptureFixture):
773+
"""Stateless terminate() should not emit INFO 'Terminating session: None'.
774+
775+
Regression test for issue #2329: in stateless mode the transport has no
776+
session id, so the prior INFO log produced 'Terminating session: None' on
777+
every request. The stateless path now logs at DEBUG with a clearer message,
778+
while the stateful path keeps the INFO-level log.
779+
"""
780+
transport = StreamableHTTPServerTransport(mcp_session_id=None)
781+
782+
with caplog.at_level(logging.DEBUG, logger="mcp.server.streamable_http"):
783+
await transport.terminate()
784+
785+
info_records = [r for r in caplog.records if r.levelno == logging.INFO]
786+
assert not any("Terminating session" in r.getMessage() for r in info_records), (
787+
"Stateless terminate() must not emit INFO 'Terminating session: ...'"
788+
)
789+
assert any(
790+
r.levelno == logging.DEBUG and "Stateless request completed" in r.getMessage() for r in caplog.records
791+
), "Stateless terminate() should log a DEBUG completion message"
792+
793+
794+
@pytest.mark.anyio
795+
async def test_terminate_stateful_log_is_info(caplog: pytest.LogCaptureFixture):
796+
"""Stateful terminate() should still log session id at INFO (#2329)."""
797+
session_id = "abc123"
798+
transport = StreamableHTTPServerTransport(mcp_session_id=session_id)
799+
800+
with caplog.at_level(logging.INFO, logger="mcp.server.streamable_http"):
801+
await transport.terminate()
802+
803+
assert any(
804+
r.levelno == logging.INFO and f"Terminating session: {session_id}" in r.getMessage() for r in caplog.records
805+
), "Stateful terminate() must still emit INFO 'Terminating session: <id>'"
806+
807+
770808
def test_session_termination(basic_server: None, basic_server_url: str):
771809
"""Test session termination via DELETE and subsequent request handling."""
772810
response = requests.post(

0 commit comments

Comments
 (0)