@@ -92,14 +92,69 @@ async def test_sse_response_disconnect_before_any_event_id_fails_request() -> No
9292
9393 async with read_stream_writer , read_stream :
9494 await transport ._handle_sse_response (response , ctx )
95- message = await read_stream .receive ()
95+ with anyio .fail_after (5 ):
96+ message = await read_stream .receive ()
9697
9798 assert isinstance (message , SessionMessage )
9899 assert isinstance (message .message , JSONRPCError )
99100 assert message .message .id == 1
100101 assert message .message .error .code == CONNECTION_CLOSED
101102
102103
104+ @pytest .mark .anyio
105+ async def test_reconnection_empty_streams_count_toward_max_attempts (monkeypatch : pytest .MonkeyPatch ) -> None :
106+ class PrimingOnlyEventSource :
107+ def __init__ (self ) -> None :
108+ self .response = httpx .Response (200 )
109+
110+ async def __aenter__ (self ) -> "PrimingOnlyEventSource" :
111+ nonlocal reconnect_attempts
112+ reconnect_attempts += 1
113+ return self
114+
115+ async def __aexit__ (self , * args : object ) -> None :
116+ return None
117+
118+ async def aiter_sse (self ) -> object :
119+ yield type (
120+ "SSE" ,
121+ (),
122+ {"event" : "message" , "data" : "" , "id" : f"event-{ reconnect_attempts } " , "retry" : 0 },
123+ )()
124+
125+ def connect_sse (* args : object , ** kwargs : object ) -> PrimingOnlyEventSource :
126+ return PrimingOnlyEventSource ()
127+
128+ reconnect_attempts = 0
129+ monkeypatch .setattr (
130+ "mcp.client.streamable_http.aconnect_sse" ,
131+ connect_sse ,
132+ )
133+
134+ transport = StreamableHTTPTransport ("http://example.com/mcp" )
135+ async with httpx .AsyncClient () as client :
136+ read_stream_writer , read_stream = create_context_streams [SessionMessage | Exception ](1 )
137+ request = JSONRPCRequest (jsonrpc = "2.0" , id = 1 , method = "tools/call" , params = {"name" : "noop" , "arguments" : {}})
138+ ctx = RequestContext (
139+ client = client ,
140+ session_id = None ,
141+ session_message = SessionMessage (request ),
142+ metadata = None ,
143+ read_stream_writer = read_stream_writer ,
144+ )
145+
146+ async with read_stream_writer , read_stream :
147+ with anyio .fail_after (5 ):
148+ await transport ._handle_reconnection (ctx , "event-1" , retry_interval_ms = 0 )
149+ message = await read_stream .receive ()
150+
151+ assert reconnect_attempts == 2
152+ assert isinstance (message , SessionMessage )
153+ assert isinstance (message .message , JSONRPCError )
154+ assert message .message .id == 1
155+ assert message .message .error .code == CONNECTION_CLOSED
156+
157+
103158@pytest .mark .anyio
104159async def test_sse_response_disconnect_ignores_closed_read_stream () -> None :
105160 transport = StreamableHTTPTransport ("http://example.com/mcp" )
0 commit comments