@@ -147,6 +147,20 @@ async def cancel(self) -> None:
147147 response = ErrorData (code = 0 , message = "Request cancelled" , data = None ),
148148 )
149149
150+ async def mark_cancelled_without_response (self ) -> None :
151+ """Cancel this request and mark it as completed without sending a response.
152+
153+ This is used when cancellation is initiated by a cancellation notification,
154+ where the receiver SHOULD NOT send a response per the MCP spec.
155+ """
156+ if not self ._entered :
157+ raise RuntimeError ("RequestResponder must be used as a context manager" )
158+ if not self ._cancel_scope :
159+ raise RuntimeError ("No active cancel scope" )
160+
161+ self ._cancel_scope .cancel ()
162+ self ._completed = True
163+
150164 @property
151165 def in_flight (self ) -> bool :
152166 return not self ._completed and not self .cancelled
@@ -314,6 +328,24 @@ async def send_notification(
314328 )
315329 await self ._write_stream .send (session_message )
316330
331+ # If we are emitting a cancellation notification for a request that we
332+ # originally sent, proactively cancel the local waiter so callers of
333+ # send_request() are unblocked without relying on a peer response.
334+ try :
335+ from mcp .types import CancelledNotification as _CancelledNotification # local import to avoid cycle
336+
337+ root = getattr (notification , "root" , None )
338+ if isinstance (root , _CancelledNotification ):
339+ cancelled_id = root .params .requestId
340+ stream = self ._response_streams .pop (cancelled_id , None )
341+ if stream is not None :
342+ error = ErrorData (code = 0 , message = "Request cancelled" , data = None )
343+ await stream .send (JSONRPCError (jsonrpc = "2.0" , id = cancelled_id , error = error ))
344+ await stream .aclose ()
345+ except Exception :
346+ # Never let local cancellation propagation break notification sending
347+ pass
348+
317349 async def _send_response (self , request_id : RequestId , response : SendResultT | ErrorData ) -> None :
318350 if isinstance (response , ErrorData ):
319351 jsonrpc_error = JSONRPCError (jsonrpc = "2.0" , id = request_id , error = response )
@@ -383,7 +415,8 @@ async def _receive_loop(self) -> None:
383415 if isinstance (notification .root , CancelledNotification ):
384416 cancelled_id = notification .root .params .requestId
385417 if cancelled_id in self ._in_flight :
386- await self ._in_flight [cancelled_id ].cancel ()
418+ # Silent cancellation in response to a cancellation notification
419+ await self ._in_flight [cancelled_id ].mark_cancelled_without_response ()
387420 else :
388421 # Handle progress notifications callback
389422 if isinstance (notification .root , ProgressNotification ):
0 commit comments