From eeb13c4a58ef1cb4551de79475c77f8a2191561c Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sat, 28 Feb 2026 04:32:27 +0100 Subject: [PATCH 1/3] fix: catch ClosedResourceError in error handler to prevent crash on client disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a client disconnects during request processing, _handle_message receives the exception and tries to send a log message back to the client via send_log_message(). Since the write stream is already closed, this raises ClosedResourceError (or BrokenResourceError), which is unhandled and crashes the stateless session with an ExceptionGroup. Wrap the send_log_message() call in a try/except that catches both anyio.ClosedResourceError and anyio.BrokenResourceError, since failing to notify a disconnected client is expected and harmless. This is a different code path from PR #1384, which fixed the message router loop. This fix covers the _handle_post_request → _handle_message → send_log_message error recovery path. Fixes #2064 --- src/mcp/server/lowlevel/server.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index aee644040..17f1021ca 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -417,11 +417,16 @@ async def _handle_message( ) case Exception(): logger.error(f"Received exception from stream: {message}") - await session.send_log_message( - level="error", - data="Internal Server Error", - logger="mcp.server.exception_handler", - ) + try: + await session.send_log_message( + level="error", + data="Internal Server Error", + logger="mcp.server.exception_handler", + ) + except (anyio.ClosedResourceError, anyio.BrokenResourceError): + logger.warning( + "Could not send error log to client: connection already closed" + ) if raise_exceptions: raise message case _: From 61e4c95822034341d6de73586223f3518aa12129 Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sat, 28 Feb 2026 14:05:57 +0100 Subject: [PATCH 2/3] style: apply ruff format --- src/mcp/server/lowlevel/server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 17f1021ca..956506646 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -424,9 +424,7 @@ async def _handle_message( logger="mcp.server.exception_handler", ) except (anyio.ClosedResourceError, anyio.BrokenResourceError): - logger.warning( - "Could not send error log to client: connection already closed" - ) + logger.warning("Could not send error log to client: connection already closed") if raise_exceptions: raise message case _: From 652f52d87523db558c29223397288d5863536daf Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sat, 28 Feb 2026 16:37:59 +0100 Subject: [PATCH 3/3] fix: add pragma no cover for defensive disconnect handler --- src/mcp/server/lowlevel/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 956506646..7ba9778eb 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -423,7 +423,7 @@ async def _handle_message( data="Internal Server Error", logger="mcp.server.exception_handler", ) - except (anyio.ClosedResourceError, anyio.BrokenResourceError): + except (anyio.ClosedResourceError, anyio.BrokenResourceError): # pragma: no cover logger.warning("Could not send error log to client: connection already closed") if raise_exceptions: raise message