8000 fix: enforce context manager usage for RequestResponder · yunstanford/python-sdk@733db0c · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit 733db0c

Browse files
committed
fix: enforce context manager usage for RequestResponder
1 parent 08cfbe5 commit 733db0c

File tree

2 files changed

+49
-11
lines changed

2 files changed

+49
-11
lines changed

src/mcp/server/session.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,20 @@ async def _received_request(
126126
case types.InitializeRequest(params=params):
127127
self._initialization_state = InitializationState.Initializing
128128
self._client_params = params
129-
await responder.respond(
130-
types.ServerResult(
131-
types.InitializeResult(
132-
protocolVersion=types.LATEST_PROTOCOL_VERSION,
133-
capabilities=self._init_options.capabilities,
134-
serverInfo=types.Implementation(
135-
name=self._init_options.server_name,
136-
version=self._init_options.server_version,
137-
),
138-
instructions=self._init_options.instructions,
129+
with responder:
130+
await responder.respond(
131+
types.ServerResult(
132+
types.InitializeResult(
133+
protocolVersion=types.LATEST_PROTOCOL_VERSION,
134+
capabilities=self._init_options.capabilities,
135+
serverInfo=types.Implementation(
136+
name=self._init_options.server_name,
137+
version=self._init_options.server_version,
138+
),
139+
instructions=self._init_options.instructions,
140+
)
139141
)
140142
)
141-
)
142143
case _:
143144
if self._initialization_state != InitializationState.Initialized:
144145
raise RuntimeError(

src/mcp/shared/session.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040

4141

4242
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+
4358
def __init__(
4459
self,
4560
request_id: RequestId,
@@ -55,19 +70,36 @@ def __init__(
5570
self._completed = False
5671
self._cancel_scope = anyio.CancelScope()
5772
self._on_complete = on_complete
73+
self._entered = False # Track if we're in a context manager
5874

5975
def __enter__(self) -> "RequestResponder[ReceiveRequestT, SendResultT]":
76+
"""Enter the context manager, enabling request cancellation tracking."""
77+
self._entered = True
78+
self._cancel_scope = anyio.CancelScope()
6079
self._cancel_scope.__enter__()
6180
return self
6281

6382
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
83+
"""Exit the context manager, performing cleanup and notifying completion."""
6484
try:
6585
if self._completed:
6686
self._on_complete(self)
6787
finally:
88+
self._entered = False
89+
if not self._cancel_scope:
90+
raise RuntimeError("No active cancel scope")
6891
self._cancel_scope.__exit__(exc_type, exc_val, exc_tb)
6992

7093
async def respond(self, response: SendResultT | ErrorData) -> None:
94+
"""Send a response for this request.
95+
96+
Must be called within a context manager block.
97+
Raises:
98+
RuntimeError: If not used within a context manager
99+
AssertionError: If request was already responded to
100+
"""
101+
if not self._entered:
102+
raise RuntimeError("RequestResponder must be used as a context manager")
71103
assert not self._completed, "Request already responded to"
72104

73105
if not self.cancelled:
@@ -79,6 +111,11 @@ async def respond(self, response: SendResultT | ErrorData) -> None:
79111

80112
async def cancel(self) -> None:
81113
"""Cancel this request and mark it as completed."""
114+
if not self._entered:
115+
raise RuntimeError("RequestResponder must be used as a context manager")
116+
if not self._cancel_scope:
117+
raise RuntimeError("No active cancel scope")
118+
82119
self._cancel_scope.cancel()
83120
self._completed = True # Mark as completed so it's removed from in_flight
84121
# Send an error response to indicate cancellation

0 commit comments

Comments
 (0)
0