-
Notifications
You must be signed in to change notification settings - Fork 462
fix(mcp): production JSON-RPC routing with sync-safe teardown (from #1169) #1175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,7 +154,7 @@ def __init__( | |
| mode=mode, | ||
| ) | ||
| self._tools_cache: Optional[List[Tool]] = None | ||
| self.use_production_mode = False | ||
| self.use_production_mode = self._mode == "production" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Core fix (Tier 1). This is the crux of #1168: Minor doc nit: this makes the module docstring (lines 14–30) stale — production now uses both |
||
| self._production_session_id: Optional[str] = None | ||
| self._production_session_lock = asyncio.Lock() | ||
| self._jsonrpc_request_id = 0 | ||
|
|
@@ -198,6 +198,27 @@ async def _production_mcp_request( | |
| response.raise_for_status() | ||
| return response.json() | ||
|
|
||
| async def _connect_async(self) -> EnvClient: | ||
| """ | ||
| Establish connection to the server. | ||
|
|
||
| In production mode (`use_production_mode=True`), open the WebSocket used | ||
| by `reset` / `step` / `state` and create a persistent HTTP MCP session | ||
| for `list_tools` / `call_tool`. Tool calls bypass `step()` over `/mcp`, | ||
| but the Gym lifecycle still requires `/ws` until production routing | ||
| covers those methods end-to-end. | ||
| """ | ||
| if getattr(self, "use_production_mode", False): | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| try: | ||
| await super()._connect_async() | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connect opens two independent sessionsHigh Severity Production Reviewed by Cursor Bugbot for commit 5951e3a. Configure here. |
||
| await self._ensure_production_session() | ||
|
Comment on lines
+213
to
+214
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regression fix confirmed. Reopening the WebSocket ( Tier 2 (non-blocking): activating HTTP |
||
| except Exception: | ||
| await self.close() | ||
| raise | ||
| return self | ||
|
|
||
| return await super()._connect_async() | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| async def _ensure_production_session(self) -> str: | ||
| """Create and cache a persistent HTTP MCP session id if needed.""" | ||
| async with self._production_session_lock: | ||
|
|
@@ -339,12 +360,16 @@ def _parse_state(self, payload: Dict[str, Any]) -> State: | |
| step_count=payload.get("step_count", 0), | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| ) | ||
|
|
||
| async def close(self) -> None: | ||
| async def _close_async(self) -> None: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sync-safe teardown crux. Overriding |
||
| """ | ||
| Close client resources. | ||
|
|
||
| In production MCP mode, this also closes the server-side persistent | ||
| MCP session (best effort) before closing websocket/provider resources. | ||
|
|
||
| Override `_close_async` rather than `close` so sync teardown | ||
| (`SyncEnvClient.close`, sync `__exit__`, and `_dispatch`) still cleans | ||
| up the HTTP MCP session. | ||
| """ | ||
| if self._production_session_id is not None: | ||
| try: | ||
|
|
@@ -366,7 +391,7 @@ async def close(self) -> None: | |
| finally: | ||
| self._http_client = None | ||
|
|
||
| await super().close() | ||
| await super()._close_async() | ||
|
|
||
|
|
||
| class MCPToolClient(MCPClientBase): | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.