Skip to content

Commit c0f3c0e

Browse files
author
Hermes Evolution
committed
fix: catch BrokenPipeError in stdio stdout_writer
When the parent process (MCP client) closes the stdout pipe during restart, kill, or graceful shutdown, stdout_writer() raises BrokenPipeError at 'await stdout.flush()'. The existing handler only catches anyio.ClosedResourceError, so the BrokenPipeError propagates through anyio's TaskGroup as an unhandled ExceptionGroup, crashing the entire MCP server process. This is a common production scenario: any MCP client restart (config reload, session reset, crash recovery) kills the server, requiring manual cleanup of stale daemon locks and process restarts. Fix: extend the except clause to also catch BrokenPipeError and ConnectionResetError, treating them as graceful shutdown signals. Tested in production with Hermes Agent (Nous Research) as the MCP client and turbo-memory-mcp as the server.
1 parent 2713b53 commit c0f3c0e

1 file changed

Lines changed: 7 additions & 1 deletion

File tree

src/mcp/server/stdio.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ async def stdout_writer():
6868
json = session_message.message.model_dump_json(by_alias=True, exclude_unset=True)
6969
await stdout.write(json + "\n")
7070
await stdout.flush()
71-
except anyio.ClosedResourceError: # pragma: no cover
71+
except (anyio.ClosedResourceError, BrokenPipeError, ConnectionResetError): # pragma: no cover
72+
# BrokenPipeError / ConnectionResetError occur when the parent
73+
# process (MCP client) closes the stdout pipe — e.g. during
74+
# restart, kill, or graceful shutdown. This is a graceful
75+
# shutdown signal, not a crash — suppress it so the server
76+
# exits cleanly instead of propagating an ExceptionGroup from
77+
# anyio's TaskGroup.
7278
await anyio.lowlevel.checkpoint()
7379

7480
async with anyio.create_task_group() as tg:

0 commit comments

Comments
 (0)