66
77import json
88
9+ import anyio
910import httpx
1011import pytest
1112from starlette .applications import Starlette
@@ -152,6 +153,19 @@ async def test_http_error_status_sends_jsonrpc_error() -> None:
152153 await session .list_tools ()
153154
154155
156+ async def test_transport_error_sends_jsonrpc_error () -> None :
157+ """Verify request transport errors unblock the pending request with an MCPError."""
158+
159+ async def raise_connect_error (request : httpx .Request ) -> httpx .Response :
160+ raise httpx .ConnectError ("All connection attempts failed" , request = request )
161+
162+ async with httpx .AsyncClient (transport = httpx .MockTransport (raise_connect_error )) as client :
163+ async with streamable_http_client ("http://localhost/mcp" , http_client = client ) as (read_stream , write_stream ):
164+ async with ClientSession (read_stream , write_stream ) as session : # pragma: no branch
165+ with pytest .raises (MCPError , match = "Transport error: All connection attempts failed" ):
166+ await session .initialize ()
167+
168+
155169async def test_http_error_on_notification_does_not_hang () -> None :
156170 """Verify HTTP errors on notifications are silently ignored.
157171
@@ -168,6 +182,23 @@ async def test_http_error_on_notification_does_not_hang() -> None:
168182 await session .send_notification (RootsListChangedNotification (method = "notifications/roots/list_changed" ))
169183
170184
185+ async def test_transport_error_on_notification_does_not_crash_transport () -> None :
186+ """Verify transport errors on notifications do not crash the transport task group."""
187+
188+ async def handle_request (request : httpx .Request ) -> httpx .Response :
189+ data = json .loads (request .content )
190+ if data .get ("method" ) == "initialize" :
191+ return httpx .Response (200 , json = {"jsonrpc" : "2.0" , "id" : data ["id" ], "result" : INIT_RESPONSE })
192+ raise httpx .ConnectError ("All connection attempts failed" , request = request )
193+
194+ async with httpx .AsyncClient (transport = httpx .MockTransport (handle_request )) as client :
195+ async with streamable_http_client ("http://localhost/mcp" , http_client = client ) as (read_stream , write_stream ):
196+ async with ClientSession (read_stream , write_stream ) as session : # pragma: no branch
197+ await session .initialize ()
198+ await session .send_notification (RootsListChangedNotification (method = "notifications/roots/list_changed" ))
199+ await anyio .sleep (0 )
200+
201+
171202def _create_invalid_json_response_app () -> Starlette :
172203 """Create a server that returns invalid JSON for requests."""
173204
0 commit comments