From c0f3c0ea384e4b8b7717fcdad877f196711c1cc3 Mon Sep 17 00:00:00 2001 From: Hermes Evolution Date: Wed, 15 Jul 2026 21:59:58 +0200 Subject: [PATCH] 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. --- src/mcp/server/stdio.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/stdio.py b/src/mcp/server/stdio.py index 876d256ddb..ef737e8c14 100644 --- a/src/mcp/server/stdio.py +++ b/src/mcp/server/stdio.py @@ -68,7 +68,13 @@ async def stdout_writer(): json = session_message.message.model_dump_json(by_alias=True, exclude_unset=True) await stdout.write(json + "\n") await stdout.flush() - except anyio.ClosedResourceError: # pragma: no cover + except (anyio.ClosedResourceError, BrokenPipeError, ConnectionResetError): # pragma: no cover + # BrokenPipeError / ConnectionResetError occur when the parent + # process (MCP client) closes the stdout pipe — e.g. during + # restart, kill, or graceful shutdown. This is a graceful + # shutdown signal, not a crash — suppress it so the server + # exits cleanly instead of propagating an ExceptionGroup from + # anyio's TaskGroup. await anyio.lowlevel.checkpoint() async with anyio.create_task_group() as tg: