From 67a25bcad75910e3a9f6d0f6e8d72d08f3196c7d Mon Sep 17 00:00:00 2001 From: strands-agent <217235299+strands-agent@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:18:36 +0000 Subject: [PATCH] fix(mcp): clear running loop state before creating background thread event loop Fixes #1512 When MCPClient runs in ASGI environments (uvicorn, hypercorn), the parent thread has an active event loop. Since PR #1444 uses contextvars.copy_context() to propagate context variables to the background thread, it also copies the _running_loop contextvar marker. When the background thread then calls run_until_complete(), Python's asyncio detects the inherited running loop marker and raises: 'Cannot run the event loop while another loop is running' The fix clears both: 1. The _running_loop contextvar via events._set_running_loop(None) 2. The thread-local event loop via asyncio.set_event_loop(None) This ensures the background thread starts with a clean slate while still preserving context variable propagation from PR #1444. Solution verified by issue reporter @jpvelasco. --- src/strands/tools/mcp/mcp_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index 1aff22a1e..b0e348320 100644 --- a/src/strands/tools/mcp/mcp_client.py +++ b/src/strands/tools/mcp/mcp_client.py @@ -730,8 +730,33 @@ def _background_task(self) -> None: sets it as the current event loop, and runs the async_background_thread coroutine until completion. In this case "until completion" means until the _close_future is resolved. This allows for a long-running event loop. + + Note: When running in ASGI environments (e.g., uvicorn), the parent thread may have + an active event loop. Since PR #1444 uses contextvars.copy_context() to propagate + context variables, the "running loop" marker may also be copied. We clear both the + running loop marker and thread-local event loop to ensure isolation. """ self._log_debug_with_thread("setting up background task event loop") + + # Clear any inherited event loop state from parent thread context. + # This is critical when running under ASGI servers (uvicorn, hypercorn) where + # the parent thread has an active event loop and contextvars.copy_context() + # copies the _running_loop contextvar. Without clearing this, run_until_complete() + # fails with "Cannot run the event loop while another loop is running". + # See: https://github.com/strands-agents/sdk-python/issues/1512 + try: + import asyncio.events as events + + events._set_running_loop(None) + except (AttributeError, RuntimeError): + # _set_running_loop may not exist in all Python versions or may fail + pass + + try: + asyncio.set_event_loop(None) + except RuntimeError: + pass + self._background_thread_event_loop = asyncio.new_event_loop() asyncio.set_event_loop(self._background_thread_event_loop) self._background_thread_event_loop.run_until_complete(self._async_background_thread())