From cd421bee5e40202be6e1f3b0bbe21d21d9e44e3d Mon Sep 17 00:00:00 2001 From: TTClaw Date: Fri, 10 Apr 2026 09:56:06 +0800 Subject: [PATCH] fix: handle cancellation race in RequestResponder.respond() When a CancelledNotification arrives after the handler completes but before respond() is called, cancel() sets _completed=True and sends an error response. The subsequent respond() call would hit an AssertionError. This change replaces the assert with a guard: if _completed is already True, respond() returns silently since the cancellation response was already sent. Fixes: #2416 _Submitted via TTClaw bounty hunter._ --- src/mcp/shared/session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 243eef5ae..c0c23dc9b 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -130,7 +130,9 @@ async def respond(self, response: SendResultT | ErrorData) -> None: """ if not self._entered: # pragma: no cover raise RuntimeError("RequestResponder must be used as a context manager") - assert not self._completed, "Request already responded to" + # Guard against race: if cancel() already set _completed, skip silently. + if self._completed: + return if not self.cancelled: # pragma: no branch self._completed = True