40
40
41
41
42
42
class RequestResponder (Generic [ReceiveRequestT , SendResultT ]):
43
+ """Handles responding to MCP requests and manages request lifecycle.
44
+
45
+ This class MUST be used as a context manager to ensure proper cleanup and
46
+ cancellation handling:
47
+
48
+ Example:
49
+ with request_responder as resp:
50
+ await resp.respond(result)
51
+
52
+ The context manager ensures:
53
+ 1. Proper cancellation scope setup and cleanup
54
+ 2. Request completion tracking
55
+ 3. Cleanup of in-flight requests
56
+ """
57
+
43
58
def __init__ (
44
59
self ,
45
60
request_id : RequestId ,
@@ -55,19 +70,33 @@ def __init__(
55
70
self ._completed = False
56
71
self ._cancel_scope = anyio .CancelScope ()
57
72
self ._on_complete = on_complete
73
+ self ._entered = False # Track if we're in a context manager
58
74
59
75
def __enter__ (self ) -> "RequestResponder[ReceiveRequestT, SendResultT]" :
76
+ """Enter the context manager, enabling request cancellation tracking."""
77
+ self ._entered = True
60
78
self ._cancel_scope .__enter__ ()
61
79
return self
62
80
63
81
def __exit__ (self , exc_type , exc_val , exc_tb ) -> None :
82
+ """Exit the context manager, performing cleanup and notifying completion."""
64
83
try :
65
84
if self ._completed :
66
85
self ._on_complete (self )
67
86
finally :
87
+ self ._entered = False
68
88
self ._cancel_scope .__exit__ (exc_type , exc_val , exc_tb )
69
89
70
90
async def respond (self , response : SendResultT | ErrorData ) -> None :
91
+ """Send a response for this request.
92
+
93
+ Must be called within a context manager block.
94
+ Raises:
95
+ RuntimeError: If not used within a context manager
96
+ AssertionError: If request was already responded to
97
+ """
98
+ if not self ._entered :
99
+ raise RuntimeError ("RequestResponder must be used as a context manager" )
71
100
assert not self ._completed , "Request already responded to"
72
101
73
102
if not self .cancelled :
0 commit comments