From 844cb26eeae37ac4e12e4b4e8241d738aea7fa0b Mon Sep 17 00:00:00 2001 From: matthew-gries Date: Fri, 7 Nov 2025 20:13:45 -0500 Subject: [PATCH] fix: Raise exceptions in default ClientSession message handler (#1401) --- src/mcp/client/session.py | 3 +++ src/mcp/shared/session.py | 5 ++-- tests/client/test_session.py | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index 8f071021d3..4f557513e3 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -58,6 +58,9 @@ async def __call__( async def _default_message_handler( message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, ) -> None: + if isinstance(message, Exception): + logging.exception("Exception in MCP client message handler") + raise message await anyio.lowlevel.checkpoint() diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 4e774984d4..1e6aaa3f32 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -415,8 +415,9 @@ async def _receive_loop(self) -> None: if stream: await stream.send(message.message.root) else: - await self._handle_incoming( - RuntimeError(f"Received response with an unknown request ID: {message}") + logging.warning( + f"Received response for unknown request ID {message.message.root.id}. " + f"Response was: {message.message.root}" ) except anyio.ClosedResourceError: diff --git a/tests/client/test_session.py b/tests/client/test_session.py index 11413d2265..1c2b83de6d 100644 --- a/tests/client/test_session.py +++ b/tests/client/test_session.py @@ -688,3 +688,53 @@ async def mock_server(): await session.initialize() await session.call_tool(name=mocked_tool.name, arguments={"foo": "bar"}, meta=meta) + + +@pytest.mark.anyio +async def test_default_message_handler_raises_exception(caplog: pytest.LogCaptureFixture): + """Test that default message handler raises exceptions it receives""" + client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1) + server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage | Exception](1) + + async def mock_server(): + session_message = await client_to_server_receive.receive() + jsonrpc_request = session_message.message + assert isinstance(jsonrpc_request.root, JSONRPCRequest) + + result = ServerResult( + InitializeResult( + protocolVersion=LATEST_PROTOCOL_VERSION, + capabilities=ServerCapabilities(), + serverInfo=Implementation(name="mock-server", version="0.1.0"), + ) + ) + + await server_to_client_send.send( + SessionMessage( + JSONRPCMessage( + JSONRPCResponse( + jsonrpc="2.0", + id=jsonrpc_request.root.id, + result=result.model_dump(by_alias=True, mode="json", exclude_none=True), + ) + ) + ) + ) + + await client_to_server_receive.receive() + await server_to_client_send.send(ValueError("Test error from transport")) + + async with ( + ClientSession(server_to_client_receive, client_to_server_send) as session, + anyio.create_task_group() as tg, + client_to_server_send, + client_to_server_receive, + server_to_client_send, + server_to_client_receive, + ): + tg.start_soon(mock_server) + await session.initialize() + await anyio.sleep(0.1) + + assert "Exception in MCP client message handler" in caplog.text + assert "Unhandled exception in receive loop" in caplog.text