Skip to content

Commit c2f9066

Browse files
committed
Surface streamable HTTP connection errors
1 parent 2472563 commit c2f9066

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

src/mcp/client/streamable_http.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,17 +467,26 @@ async def _handle_message(session_message: SessionMessage) -> None:
467467
read_stream_writer=read_stream_writer,
468468
)
469469

470-
async def handle_request_async():
470+
async def send_message() -> None:
471471
if is_resumption:
472472
await self._handle_resumption_request(ctx)
473473
else:
474474
await self._handle_post_request(ctx)
475475

476+
async def handle_request_async(request: JSONRPCRequest) -> None:
477+
try:
478+
await send_message()
479+
except httpx.TransportError as exc:
480+
logger.debug("Error handling request", exc_info=True)
481+
error_data = ErrorData(code=INTERNAL_ERROR, message=f"Transport error: {exc}")
482+
error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=request.id, error=error_data))
483+
await ctx.read_stream_writer.send(error_msg)
484+
476485
# If this is a request, start a new task to handle it
477486
if isinstance(message, JSONRPCRequest):
478-
tg.start_soon(handle_request_async)
487+
tg.start_soon(handle_request_async, message)
479488
else:
480-
await handle_request_async()
489+
await send_message()
481490

482491
async for session_message in write_stream_reader:
483492
sender_ctx = write_stream_reader.last_context

tests/client/test_session_group.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,25 @@ async def test_client_session_group_disconnect_non_existent_server():
278278
await group.disconnect_from_server(session)
279279

280280

281+
@pytest.mark.anyio
282+
async def test_client_session_group_streamable_http_connection_error_surfaces() -> None:
283+
async def fail_request(request: httpx.Request) -> httpx.Response:
284+
raise httpx.ConnectError("offline", request=request)
285+
286+
http_client = httpx.AsyncClient(transport=httpx.MockTransport(fail_request))
287+
288+
with mock.patch("mcp.client.session_group.create_mcp_http_client", return_value=http_client):
289+
async with ClientSessionGroup() as group:
290+
with pytest.raises(MCPError) as excinfo:
291+
await group.connect_to_server(
292+
StreamableHttpParameters(url="http://example.test/mcp"),
293+
ClientSessionParameters(read_timeout_seconds=2),
294+
)
295+
296+
assert excinfo.value.error.code == types.INTERNAL_ERROR
297+
assert excinfo.value.error.message == "Transport error: offline"
298+
299+
281300
# TODO(Marcelo): This is horrible. We should drop this test.
282301
@pytest.mark.anyio
283302
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)