Skip to content

Commit 844cb26

Browse files
committed
fix: Raise exceptions in default ClientSession message handler (#1401)
1 parent 9eae96a commit 844cb26

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

src/mcp/client/session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ async def __call__(
5858
async def _default_message_handler(
5959
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
6060
) -> None:
61+
if isinstance(message, Exception):
62+
logging.exception("Exception in MCP client message handler")
63+
raise message
6164
await anyio.lowlevel.checkpoint()
6265

6366

src/mcp/shared/session.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,9 @@ async def _receive_loop(self) -> None:
415415
if stream:
416416
await stream.send(message.message.root)
417417
else:
418-
await self._handle_incoming(
419-
RuntimeError(f"Received response with an unknown request ID: {message}")
418+
logging.warning(
419+
f"Received response for unknown request ID {message.message.root.id}. "
420+
f"Response was: {message.message.root}"
420421
)
421422

422423
except anyio.ClosedResourceError:

tests/client/test_session.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,53 @@ async def mock_server():
688688
await session.initialize()
689689

690690
await session.call_tool(name=mocked_tool.name, arguments={"foo": "bar"}, meta=meta)
691+
692+
693+
@pytest.mark.anyio
694+
async def test_default_message_handler_raises_exception(caplog: pytest.LogCaptureFixture):
695+
"""Test that default message handler raises exceptions it receives"""
696+
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1)
697+
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage | Exception](1)
698+
699+
async def mock_server():
700+
session_message = await client_to_server_receive.receive()
701+
jsonrpc_request = session_message.message
702+
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
703+
704+
result = ServerResult(
705+
InitializeResult(
706+
protocolVersion=LATEST_PROTOCOL_VERSION,
707+
capabilities=ServerCapabilities(),
708+
serverInfo=Implementation(name="mock-server", version="0.1.0"),
709+
)
710+
)
711+
712+
await server_to_client_send.send(
713+
SessionMessage(
714+
JSONRPCMessage(
715+
JSONRPCResponse(
716+
jsonrpc="2.0",
717+
id=jsonrpc_request.root.id,
718+
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
719+
)
720+
)
721+
)
722+
)
723+
724+
await client_to_server_receive.receive()
725+
await server_to_client_send.send(ValueError("Test error from transport"))
726+
727+
async with (
728+
ClientSession(server_to_client_receive, client_to_server_send) as session,
729+
anyio.create_task_group() as tg,
730+
client_to_server_send,
731+
client_to_server_receive,
732+
server_to_client_send,
733+
server_to_client_receive,
734+
):
735+
tg.start_soon(mock_server)
736+
await session.initialize()
737+
await anyio.sleep(0.1)
738+
739+
assert "Exception in MCP client message handler" in caplog.text
740+
assert "Unhandled exception in receive loop" in caplog.text

0 commit comments

Comments
 (0)