-
Notifications
You must be signed in to change notification settings - Fork 697
Description
Checks
- I have updated to the lastest minor and patch version of Strands
- I have checked the documentation and this is not expected behavior
- I have searched ./issues and there are no duplicates of my issue
Strands Version
1.26.0
Python Version
3.14
Operating System
Linux (Debian-based, Docker container)
Installation Method
pip
Steps to Reproduce
Description:
When using stateless_http=True with json_response=True, client disconnections cause _handle_message to crash. The exception handler at lowlevel/server.py:699-705
catches the stream exception but then calls session.send_log_message(), which writes to the already-closed _write_stream. This raises anyio.ClosedResourceError,
which propagates as an ExceptionGroup through the task group and kills the session.
Reproduction:
- Create a server with FastMCP(stateless_http=True, json_response=True)
- Register a tool that takes a few seconds to execute
- Have the client disconnect (close HTTP connection) while the server is processing or during cleanup
- Server logs 4 errors: Error handling POST request, Received exception from stream, Stateless session crashed
Stack trace:
ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
mcp/server/lowlevel/server.py:701 _handle_message
→ session.send_log_message(level="error", data="Internal Server Error", ...)
mcp/server/session.py:213 send_log_message
→ send_notification()
mcp/shared/session.py:335 send_notification
→ self._write_stream.send(session_message)
anyio/streams/memory.py:218 send_nowait
→ raise ClosedResourceError
Root cause:
In lowlevel/server.py:699-705:
case Exception():
logger.error(f"Received exception from stream: {message}")
await session.send_log_message( # ← crashes here
level="error",
data="Internal Server Error",
logger="mcp.server.exception_handler",
)
The send_log_message call is not guarded against ClosedResourceError / BrokenResourceError. When the client has already disconnected, the write stream is closed,
and the notification delivery fails.
Impact:
Under concurrent load (multiple clients), client disconnections cause noisy error logs and kill sessions that are already in teardown. The crash is caught by
streamable_http_manager.py:187 (except Exception: logger.exception("Stateless session crashed")), so it doesn't bring down the server process, but it disrupts
the session's task group and any sibling tasks within it.
Expected Behavior
When a client disconnects, the server should gracefully close the session without raising an unhandled exception. The send_log_message call in the exception
handler should be guarded against write failures — if the client is already gone, there's no one to receive the notification, so the error should be silently
discarded (or logged at DEBUG level).
Actual Behavior
The server crashes the stateless session with an unhandled ExceptionGroup containing anyio.ClosedResourceError. The exception handler at
lowlevel/server.py:699-705 attempts to send a log notification to the disconnected client, which fails because the write stream is already closed. This
propagates through the task group, killing the session and producing 4 error-level log lines (Error handling POST request, Received exception from stream,
Stateless session crashed) per disconnected request.
Additional Context
No response
Possible Solution
Wrap the send_log_message call in a try/except:
case Exception():
logger.error(f"Received exception from stream: {message}")
try:
await session.send_log_message(
level="error",
data="Internal Server Error",
logger="mcp.server.exception_handler",
)
except (anyio.ClosedResourceError, anyio.BrokenResourceError):
pass # Client already disconnected, nothing to notify
Related Issues
No response