Skip to content

Commit 20209d6

Browse files
author
BashNetCorp
committed
[v1.x] fix(stdio): handle BrokenResourceError in stdout_reader (#1960)
Fix the race condition where `read_stream_writer.aclose()` in the `finally` block of `stdio_client` can close the stream while `stdout_reader` is mid-`send`, producing an unhandled `anyio.BrokenResourceError` that propagates through the task group and surfaces as an `ExceptionGroup` to the caller. Root cause: the two `send` sites in `stdout_reader` did not catch `BrokenResourceError`. The outer `except` only caught `ClosedResourceError`, which is a distinct anyio exception class raised on already-closed streams. `BrokenResourceError` is raised when the receiver is closed while a `send` is in flight, which is the exact shape of this shutdown race. Fix: wrap each `read_stream_writer.send(...)` call in a `try`/`except` that catches both `ClosedResourceError` and `BrokenResourceError`, and return cleanly. Also widen the outer `except` to the same union for defense in depth. Verified by driving a stdio MCP server (jules-mcp-server) through mcp2cli, which previously always surfaced the ExceptionGroup traceback; after the patch, tool calls return cleanly. Github-Issue:#1960
1 parent 73d458b commit 20209d6

File tree

1 file changed

+13
-3
lines changed

1 file changed

+13
-3
lines changed

src/mcp/client/stdio/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,22 @@ async def stdout_reader():
155155
message = types.JSONRPCMessage.model_validate_json(line)
156156
except Exception as exc: # pragma: no cover
157157
logger.exception("Failed to parse JSONRPC message from server")
158-
await read_stream_writer.send(exc)
158+
try:
159+
await read_stream_writer.send(exc)
160+
except (anyio.ClosedResourceError, anyio.BrokenResourceError):
161+
# Context is closing; exit gracefully (issue #1960).
162+
return
159163
continue
160164

161165
session_message = SessionMessage(message)
162-
await read_stream_writer.send(session_message)
163-
except anyio.ClosedResourceError: # pragma: no cover
166+
try:
167+
await read_stream_writer.send(session_message)
168+
except (anyio.ClosedResourceError, anyio.BrokenResourceError):
169+
# Context is closing; exit gracefully (issue #1960).
170+
# Happens when the caller exits the stdio_client context
171+
# while the subprocess is still writing to stdout.
172+
return
173+
except (anyio.ClosedResourceError, anyio.BrokenResourceError): # pragma: no cover
164174
await anyio.lowlevel.checkpoint()
165175

166176
async def stdin_writer():

0 commit comments

Comments
 (0)