From 925a02b1c264a8a0f07809b18f3f9bf6146757df Mon Sep 17 00:00:00 2001 From: Jianke LIN Date: Fri, 29 May 2026 22:10:51 +0200 Subject: [PATCH] fix(stdio): suppress BrokenResourceError on shutdown --- src/mcp/client/stdio.py | 2 +- tests/client/test_stdio.py | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/mcp/client/stdio.py b/src/mcp/client/stdio.py index 902dc8576c..9160843c2c 100644 --- a/src/mcp/client/stdio.py +++ b/src/mcp/client/stdio.py @@ -158,7 +158,7 @@ async def stdout_reader(): session_message = SessionMessage(message) await read_stream_writer.send(session_message) - except anyio.ClosedResourceError: # pragma: lax no cover + except (anyio.ClosedResourceError, anyio.BrokenResourceError): await anyio.lowlevel.checkpoint() async def stdin_writer(): diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index 06e2cba4b1..db617f94a7 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -7,6 +7,7 @@ import anyio import anyio.abc +import anyio.lowlevel import pytest from mcp.client.session import ClientSession @@ -67,7 +68,29 @@ async def test_stdio_client(): assert len(read_messages) == 2 assert read_messages[0] == JSONRPCRequest(jsonrpc="2.0", id=1, method="ping") - assert read_messages[1] == JSONRPCResponse(jsonrpc="2.0", id=2, result={}) + assert read_messages[1] == JSONRPCResponse(jsonrpc="2.0", id=2, result={}) + + +@pytest.mark.anyio +async def test_stdio_client_exits_cleanly_while_stdout_reader_is_blocked_in_send(): + server_script = textwrap.dedent( + r""" + import sys + + sys.stdout.write('{"jsonrpc":"2.0","id":1,"result":{}}\n') + sys.stdout.flush() + + # Wait for stdin to close so the parent can exercise the shutdown path. + for _line in sys.stdin: + pass + """ + ) + server_params = StdioServerParameters(command=sys.executable, args=["-c", server_script]) + + async with stdio_client(server_params) as (read_stream, _write_stream): + with anyio.fail_after(5.0): + while read_stream.statistics().tasks_waiting_send == 0: + await anyio.lowlevel.checkpoint() @pytest.mark.anyio