10
10
11
11
from mcp .shared .exceptions import McpError
12
12
from mcp .types import (
13
+ CancelledNotification ,
13
14
ClientNotification ,
14
15
ClientRequest ,
15
16
ClientResult ,
@@ -44,20 +45,36 @@ def __init__(
44
45
request_meta : RequestParams .Meta | None ,
45
46
request : ReceiveRequestT ,
46
47
session : "BaseSession" ,
48
+ cancel_scope : anyio .CancelScope | None ,
47
49
) -> None :
48
50
self .request_id = request_id
49
51
self .request_meta = request_meta
50
52
self .request = request
51
53
self ._session = session
52
54
self ._responded = False
55
+ self ._cancel_scope = cancel_scope
53
56
54
57
async def respond (self , response : SendResultT | ErrorData ) -> None :
55
58
assert not self ._responded , "Request already responded to"
56
- self ._responded = True
57
59
58
- await self ._session ._send_response (
59
- request_id = self .request_id , response = response
60
- )
60
+ if not self .cancelled :
61
+ self ._responded = True
62
+
63
+ await self ._session ._send_response (
64
+ request_id = self .request_id , response = response
65
+ )
66
+
67
+ async def cancel (self ) -> None :
68
+ if self ._cancel_scope is not None :
69
+ self ._cancel_scope .cancel ()
70
+
71
+ @property
72
+ def in_flight (self ) -> bool :
73
+ return not self ._responded and not self .cancelled
74
+
75
+ @property
76
+ def cancelled (self ) -> bool :
77
+ return self ._cancel_scope is not None and self ._cancel_scope .cancel_called
61
78
62
79
63
80
class BaseSession (
@@ -205,12 +222,21 @@ async def _send_response(
205
222
await self ._write_stream .send (JSONRPCMessage (jsonrpc_response ))
206
223
207
224
async def _receive_loop (self ) -> None :
225
+ in_flight : dict [RequestId , RequestResponder ] = {}
226
+
208
227
async with (
209
228
self ._read_stream ,
210
229
self ._write_stream ,
211
230
self ._incoming_message_stream_writer ,
212
231
):
213
232
async for message in self ._read_stream :
233
+ # Clean up completed requests
234
+ in_flight = {
235
+ req_id : responder
236
+ for req_id , responder in in_flight .items ()
237
+ if responder .in_flight
238
+ }
239
+
214
240
if isinstance (message , Exception ):
215
241
await self ._incoming_message_stream_writer .send (message )
216
242
elif isinstance (message .root , JSONRPCRequest ):
@@ -219,27 +245,38 @@ async def _receive_loop(self) -> None:
219
245
by_alias = True , mode = "json" , exclude_none = True
220
246
)
221
247
)
222
- responder = RequestResponder (
223
- request_id = message .root .id ,
224
- request_meta = validated_request .root .params .meta
225
- if validated_request .root .params
226
- else None ,
227
- request = validated_request ,
228
- session = self ,
229
- )
230
248
231
- await self ._received_request (responder )
232
- if not responder ._responded :
233
- await self ._incoming_message_stream_writer .send (responder )
249
+ with anyio .CancelScope () as scope :
250
+ responder = RequestResponder (
251
+ request_id = message .root .id ,
252
+ request_meta = validated_request .root .params .meta
253
+ if validated_request .root .params
254
+ else None ,
255
+ request = validated_request ,
256
+ session = self ,
257
+ cancel_scope = scope ,
258
+ )
259
+
260
+ in_flight [message .root .id ] = responder
261
+
262
+ await self ._received_request (responder )
263
+ if not responder ._responded :
264
+ await self ._incoming_message_stream_writer .send (responder )
265
+
234
266
elif isinstance (message .root , JSONRPCNotification ):
235
267
notification = self ._receive_notification_type .model_validate (
236
268
message .root .model_dump (
237
269
by_alias = True , mode = "json" , exclude_none = True
238
270
)
239
271
)
240
-
241
- await self ._received_notification (notification )
242
- await self ._incoming_message_stream_writer .send (notification )
272
+ # Handle cancellation notifications
273
+ if isinstance (notification .root , CancelledNotification ):
274
+ cancelled_id = notification .root .params .requestId
275
+ if cancelled_id in in_flight :
276
+ await in_flight [cancelled_id ].cancel ()
277
+ else :
278
+ await self ._received_notification (notification )
279
+ await self ._incoming_message_stream_writer .send (notification )
243
280
else : # Response or error
244
281
stream = self ._response_streams .pop (message .root .id , None )
245
282
if stream :
0 commit comments