1212
1313
1414async def _raise_closed_resource_error () -> None :
15+ """Raise the exact exception the disconnect race produces."""
1516 raise anyio .ClosedResourceError ()
1617
1718
1819async def _raise_runtime_error () -> None :
20+ """Raise an unrelated failure, standing in for a real server bug."""
1921 raise RuntimeError ("real bug" )
2022
2123
22- async def _run_in_task_group (* coro_funcs ) -> BaseException :
24+ async def _run_in_task_group (* coro_funcs ) -> Exception :
2325 """Run each of ``coro_funcs`` as a task-group child and return the raised
2426 exception -- reproducing the actual shape mcp.server.lowlevel.server.Server.run()
2527 produces: it dispatches each incoming message via
@@ -28,12 +30,17 @@ async def _run_in_task_group(*coro_funcs) -> BaseException:
2830 child. anyio always wraps that in an ExceptionGroup, even for a single
2931 failure -- a hand-built ``exc_info`` with a bare exception does not
3032 reproduce that boundary.
33+
34+ Narrowed to ``Exception`` (not ``BaseException``): the child coroutines
35+ here only ever raise ``Exception`` subclasses, so anyio's task group
36+ wraps them in ``ExceptionGroup`` (an ``Exception``), never
37+ ``BaseExceptionGroup``-only content like a cancellation.
3138 """
3239 try :
3340 async with anyio .create_task_group () as tg :
3441 for coro_func in coro_funcs :
3542 tg .start_soon (coro_func )
36- except BaseException as exc :
43+ except Exception as exc :
3744 return exc
3845 raise AssertionError ("task group did not raise" )
3946
@@ -42,6 +49,7 @@ class TestSessionDisconnectLogFilter:
4249 """Verify the filter demotes disconnect-caused 'session crashed' tracebacks."""
4350
4451 def setup_method (self ):
52+ """Create a fresh filter instance for each test."""
4553 self .log_filter = SessionDisconnectLogFilter ()
4654
4755 def _make_record (
@@ -50,6 +58,7 @@ def _make_record(
5058 msg : str ,
5159 exc : BaseException | None ,
5260 ) -> logging .LogRecord :
61+ """Build a bare LogRecord carrying ``exc`` as its exc_info, if given."""
5362 exc_info = (type (exc ), exc , None ) if exc is not None else None
5463 return logging .LogRecord (
5564 name = name ,
@@ -62,8 +71,8 @@ def _make_record(
6271 )
6372
6473 def test_demotes_stateless_session_crash_from_closed_resource_error (self ):
65- # Exactly what mcp/server/streamable_http_manager.py's
66- # _handle_stateless_request logs when the client already disconnected.
74+ """ Exactly what mcp/server/streamable_http_manager.py's
75+ _handle_stateless_request logs when the client already disconnected."""
6776 err = anyio .ClosedResourceError ()
6877 record = self ._make_record (
6978 "mcp.server.streamable_http_manager" ,
@@ -79,8 +88,8 @@ def test_demotes_stateless_session_crash_from_closed_resource_error(self):
7988 assert "Stateless session crashed" in record .getMessage ()
8089
8190 def test_demotes_stateful_session_crash_from_closed_resource_error (self ):
82- # The stateful runner's equivalent log line -- same race, same fix,
83- # even though every HTTP entry point currently forces stateless_http.
91+ """ The stateful runner's equivalent log line -- same race, same fix,
92+ even though every HTTP entry point currently forces stateless_http."""
8493 err = anyio .ClosedResourceError ()
8594 record = self ._make_record (
8695 "mcp.server.streamable_http_manager" ,
@@ -92,10 +101,10 @@ def test_demotes_stateful_session_crash_from_closed_resource_error(self):
92101 assert record .exc_info is None
93102
94103 async def test_demotes_real_task_group_exception_group (self ):
95- # The shape actually logged in production: mcp.server.lowlevel.server
96- # dispatches message handling via anyio.create_task_group().start_soon,
97- # so a ClosedResourceError from _send_response arrives here wrapped in
98- # an ExceptionGroup, not as a bare exception.
104+ """ The shape actually logged in production: mcp.server.lowlevel.server
105+ dispatches message handling via anyio.create_task_group().start_soon,
106+ so a ClosedResourceError from _send_response arrives here wrapped in
107+ an ExceptionGroup, not as a bare exception."""
99108 caught = await _run_in_task_group (_raise_closed_resource_error )
100109 assert isinstance (caught , BaseExceptionGroup )
101110
@@ -110,9 +119,9 @@ async def test_demotes_real_task_group_exception_group(self):
110119 assert "client disconnected before response delivery" in record .getMessage ()
111120
112121 async def test_leaves_mixed_exception_group_at_error (self ):
113- # A task group with one ClosedResourceError AND one unrelated failure
114- # signals a real problem alongside the expected disconnect race -- the
115- # whole record must stay at ERROR with its traceback intact.
122+ """ A task group with one ClosedResourceError AND one unrelated failure
123+ signals a real problem alongside the expected disconnect race -- the
124+ whole record must stay at ERROR with its traceback intact."""
116125 caught = await _run_in_task_group (
117126 _raise_closed_resource_error , _raise_runtime_error
118127 )
@@ -129,8 +138,8 @@ async def test_leaves_mixed_exception_group_at_error(self):
129138 assert record .exc_info is original_exc_info
130139
131140 def test_passes_bare_exception_through_untouched (self ):
132- # An actual server bug on this logger must keep its traceback and
133- # ERROR level -- only the known-benign disconnect race is demoted.
141+ """ An actual server bug on this logger must keep its traceback and
142+ ERROR level -- only the known-benign disconnect race is demoted."""
134143 err = RuntimeError ("server bug" )
135144 record = self ._make_record (
136145 "mcp.server.streamable_http_manager" ,
@@ -143,6 +152,7 @@ def test_passes_bare_exception_through_untouched(self):
143152 assert record .exc_info is original_exc_info
144153
145154 def test_leaves_other_loggers_unchanged (self ):
155+ """Only the SDK's session-manager logger is ever touched."""
146156 err = anyio .ClosedResourceError ()
147157 record = self ._make_record (
148158 "some.other.logger" ,
@@ -154,6 +164,7 @@ def test_leaves_other_loggers_unchanged(self):
154164 assert record .exc_info is not None
155165
156166 def test_passes_record_without_exc_info (self ):
167+ """A record with no attached exception is never touched."""
157168 record = self ._make_record (
158169 "mcp.server.streamable_http_manager" ,
159170 "Stateless session crashed" ,
@@ -167,33 +178,42 @@ class TestIsOnlyClosedResourceErrors:
167178 """Direct coverage of the recursive classifier the filter relies on."""
168179
169180 def test_bare_closed_resource_error (self ):
181+ """A bare ClosedResourceError matches on its own."""
170182 assert _is_only_closed_resource_errors (anyio .ClosedResourceError ()) is True
171183
172184 def test_bare_other_exception (self ):
185+ """An unrelated bare exception never matches."""
173186 assert _is_only_closed_resource_errors (RuntimeError ("x" )) is False
174187
175188 def test_group_of_one_closed_resource_error (self ):
189+ """A single-item group wrapping just the known-benign exception matches."""
176190 group = ExceptionGroup ("eg" , [anyio .ClosedResourceError ()])
177191 assert _is_only_closed_resource_errors (group ) is True
178192
179193 def test_nested_group_of_closed_resource_errors (self ):
194+ """Nested groups are unwrapped recursively, matching all-benign leaves."""
180195 inner = ExceptionGroup ("inner" , [anyio .ClosedResourceError ()])
181196 outer = ExceptionGroup ("outer" , [inner , anyio .ClosedResourceError ()])
182197 assert _is_only_closed_resource_errors (outer ) is True
183198
184199 def test_mixed_group_is_rejected (self ):
200+ """A group with even one non-benign leaf must not match."""
185201 group = ExceptionGroup ("eg" , [anyio .ClosedResourceError (), RuntimeError ("x" )])
186202 assert _is_only_closed_resource_errors (group ) is False
187203
188204 def test_empty_group_is_rejected (self ):
189- # Defensive: an ExceptionGroup always carries at least one exception
190- # in practice, but `all([])` is vacuously True -- guard against ever
191- # demoting on a group with nothing in it.
205+ """Defensive: an ExceptionGroup always carries at least one exception
206+ in practice, but `all([])` is vacuously True -- guard against ever
207+ demoting on a group with nothing in it. ExceptionGroup itself refuses
208+ to construct empty, so this pins that guarantee rather than exercising
209+ the classifier's own guard directly."""
192210 with pytest .raises (ValueError ):
193211 ExceptionGroup ("empty" , [])
194212
195213
196214class TestSessionDisconnectLogFilterWiring :
215+ """End-to-end: _setup_logging wires the filter onto real logger output."""
216+
197217 def test_setup_logging_wires_filter_and_demotes_output (self , monkeypatch ):
198218 """Integration: ``_setup_logging`` attaches the filter to the SDK's
199219 session-manager logger, so a real ``ClosedResourceError``-caused
0 commit comments