Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mcp/client/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
25 changes: 24 additions & 1 deletion tests/client/test_stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import anyio
import anyio.abc
import anyio.lowlevel
import pytest

from mcp.client.session import ClientSession
Expand Down Expand Up @@ -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
Expand Down
Loading