Skip to content

Commit c1fffe8

Browse files
committed
test: cover branch where reconnection exhausts max attempts
Add test_sse_error_when_reconnection_exhausted to exercise the _handle_sse_response path where SSE events are received (setting last_event_id) but reconnection fails, ensuring the JSONRPCError is sent to unblock the waiting request.
1 parent ae0bc81 commit c1fffe8

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

src/mcp/client/streamable_http.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,7 @@ async def handle_get_stream(self, client: httpx.AsyncClient, read_stream_writer:
211211

212212
# Only reset attempts if we actually received events;
213213
# empty connections count toward MAX_RECONNECTION_ATTEMPTS
214-
if received_events:
215-
attempt = 0
216-
else:
217-
attempt += 1
214+
attempt = 0 if received_events else attempt + 1
218215

219216
except Exception: # pragma: lax no cover
220217
logger.debug("GET stream error", exc_info=True)

tests/shared/test_streamable_http.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,15 +2266,42 @@ async def test_sse_read_timeout_propagates_error(basic_server: None, basic_serve
22662266
read_stream,
22672267
write_stream,
22682268
):
2269-
async with ClientSession(read_stream, write_stream) as session:
2269+
async with ClientSession(read_stream, write_stream) as session: # pragma: no branch
22702270
await session.initialize()
22712271

22722272
# Read a "slow" resource that takes 2s — longer than our 0.5s read timeout
2273-
with pytest.raises(MCPError):
2274-
with anyio.fail_after(10):
2273+
with pytest.raises(MCPError): # pragma: no branch
2274+
with anyio.fail_after(10): # pragma: no branch
22752275
await session.read_resource("slow://test")
22762276

22772277

2278+
@pytest.mark.anyio
2279+
async def test_sse_error_when_reconnection_exhausted(
2280+
event_server: tuple[SimpleEventStore, str],
2281+
monkeypatch: pytest.MonkeyPatch,
2282+
):
2283+
"""When SSE stream closes after events and reconnection fails, MCPError is raised."""
2284+
_, server_url = event_server
2285+
2286+
async def _always_fail_reconnection(
2287+
self: Any, ctx: Any, last_event_id: Any, retry_interval_ms: Any = None, attempt: int = 0
2288+
) -> bool:
2289+
return False
2290+
2291+
monkeypatch.setattr(StreamableHTTPTransport, "_handle_reconnection", _always_fail_reconnection)
2292+
2293+
async with streamable_http_client(f"{server_url}/mcp") as (read_stream, write_stream):
2294+
async with ClientSession(read_stream, write_stream) as session: # pragma: no branch
2295+
await session.initialize()
2296+
2297+
# tool_with_stream_close sends a priming event (setting last_event_id),
2298+
# then closes the SSE stream. With reconnection patched to fail,
2299+
# _handle_sse_response falls through to send the error.
2300+
with pytest.raises(MCPError): # pragma: no branch
2301+
with anyio.fail_after(10): # pragma: no branch
2302+
await session.call_tool("tool_with_stream_close", {})
2303+
2304+
22782305
@pytest.mark.anyio
22792306
async def test_handle_reconnection_returns_false_on_max_attempts():
22802307
"""_handle_reconnection returns False when max attempts exceeded."""

0 commit comments

Comments
 (0)