Skip to content

Commit 7534b37

Browse files
feat: add a flag on cleanup to protect change on dict
Since we are using for-loop while cleaning up _response_streams dict, the dict must not be changed on cleanup. We could handle this issue easily by adding a simple flag.
1 parent 606e4c8 commit 7534b37

File tree

1 file changed

+8
-2
lines changed

1 file changed

+8
-2
lines changed

src/mcp/shared/session.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class BaseSession(
180180
_in_flight: dict[RequestId, RequestResponder[ReceiveRequestT, SendResultT]]
181181
_progress_callbacks: dict[RequestId, ProgressFnT]
182182
_response_routers: list["ResponseRouter"]
183+
_closing: bool = False
183184

184185
def __init__(
185186
self,
@@ -252,6 +253,9 @@ async def send_request(
252253
Do not use this method to emit notifications! Use send_notification()
253254
instead.
254255
"""
256+
if self._closing:
257+
raise McpError(ErrorData(code=CONNECTION_CLOSED, message="Connection closed"))
258+
255259
request_id = self._request_id
256260
self._request_id = request_id + 1
257261

@@ -307,7 +311,8 @@ async def send_request(
307311
return result_type.model_validate(response_or_error.result)
308312

309313
finally:
310-
self._response_streams.pop(request_id, None)
314+
self._response_streams.pop(request_id, None) if not self._closing else None
315+
311316
self._progress_callbacks.pop(request_id, None)
312317
await response_stream.aclose()
313318
await response_stream_reader.aclose()
@@ -444,6 +449,7 @@ async def _receive_loop(self) -> None:
444449
finally:
445450
# after the read stream is closed, we need to send errors
446451
# to any pending requests
452+
self._closing = True
447453
with anyio.CancelScope(shield=True):
448454
for id, stream in self._response_streams.items():
449455
error = ErrorData(code=CONNECTION_CLOSED, message="Connection closed")
@@ -509,7 +515,7 @@ async def _handle_response(self, message: SessionMessage) -> None:
509515
return # Handled
510516

511517
# Fall back to normal response streams
512-
stream = self._response_streams.pop(response_id, None)
518+
stream = self._response_streams.pop(response_id, None) if not self._closing else None
513519
if stream: # pragma: no cover
514520
await stream.send(root)
515521
else: # pragma: no cover

0 commit comments

Comments
 (0)