@@ -147,6 +147,20 @@ async def cancel(self) -> None:
147
147
response = ErrorData (code = 0 , message = "Request cancelled" , data = None ),
148
148
)
149
149
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
+
150
164
@property
151
165
def in_flight (self ) -> bool :
152
166
return not self ._completed and not self .cancelled
@@ -314,6 +328,24 @@ async def send_notification(
314
328
)
315
329
await self ._write_stream .send (session_message )
316
330
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
+
317
349
async def _send_response (self , request_id : RequestId , response : SendResultT | ErrorData ) -> None :
318
350
if isinstance (response , ErrorData ):
319
351
jsonrpc_error = JSONRPCError (jsonrpc = "2.0" , id = request_id , error = response )
@@ -383,7 +415,8 @@ async def _receive_loop(self) -> None:
383
415
if isinstance (notification .root , CancelledNotification ):
384
416
cancelled_id = notification .root .params .requestId
385
417
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 ()
387
420
else :
388
421
# Handle progress notifications callback
389
422
if isinstance (notification .root , ProgressNotification ):
0 commit comments