fix(runtime): avoid RuntimeError when calling get_tool_specs with MCP tools from running event loop
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 74/100
Línea de trabajo
Empieza en runtime/node/agent/tool/tool_manager.py, donde las dos llamadas a asyncio.run() se encuentran cerca de las líneas 198-204 (la obtención de MCP por HTTP) y la línea 227 (la obtención de MCP por stdio), a las que se llega desde get_tool_specs. Reproduce el problema con el ejemplo mínimo del issue, llamando a get_tool_specs desde dentro de una función async. Está terminado cuando esa llamada devuelve las especificaciones de herramientas MCP sin un RuntimeError, tanto fuera como dentro de un event loop en ejecución.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary & Context
In runtime/node/agent/tool/tool_manager.py, ToolManager.get_tool_specs() provides synchronous tool specification discovery for agent nodes (runtime/node/executor/agent_executor.py:77).
When configuring MCP tools (mcp_remote or mcp_local), _build_mcp_remote_specs() and _build_mcp_local_specs() invoke asyncio.run() to execute coroutines synchronously:
# Lines 198-204:
tools = asyncio.run(
self._fetch_mcp_tools_http(
config.server,
headers=config.headers,
timeout=config.timeout,
)
)
# Line 227:
tools = asyncio.run(self._fetch_mcp_tools_stdio(config, launch_key))
Problem & Impact
When an agent node with MCP tools is executed inside an asynchronous environment—such as FastAPI routes (server/routes/websocket.py, server/routes/execute.py), an async workflow executor, or an async test—an asyncio event loop is already active in the current thread.
Calling asyncio.run() while an event loop is active immediately raises:
RuntimeError: asyncio.run() cannot be called from a running event loop
This crashes the node execution and prevents MCP tools from being fetched or registered.
Minimal Reproducible Example
import asyncio
from runtime.node.agent.tool.tool_manager import ToolManager
from entity.configs.node.tooling import McpRemoteConfig, ToolingConfig
tool_manager = ToolManager()
mcp_cfg = ToolingConfig(
type="mcp_remote",
config=McpRemoteConfig(server="http://localhost:8000/sse", path="node.tooling.mcp_remote"),
path="node.tooling"
)
# Inside any async function (e.g. FastAPI endpoint or async runner):
async def run_step():
# Calling get_tool_specs invokes asyncio.run() internally:
return tool_manager.get_tool_specs([mcp_cfg])
asyncio.run(run_step())
# Result: RuntimeError: asyncio.run() cannot be called from a running event loop
Proposed Fix
Implement a thread-safe coroutine runner helper that detects if an event loop is already active in the current thread. If an event loop is running, delegate the coroutine execution to a worker thread via concurrent.futures.ThreadPoolExecutor to avoid RuntimeError:
import concurrent.futures
def _run_sync(coro):
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop is not None and loop.is_running():
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
return pool.submit(asyncio.run, coro).result()
return asyncio.run(coro)
And update lines 198 and 227 in tool_manager.py to use _run_sync(...).
Empirical Verification
Tested with local reproduction script:
- Gate 1 (Outside event loop): Tools retrieved synchronously with 100% success.
- Gate 2 (Inside running event loop): Successfully executes coroutine in worker thread and returns tool specs without raising
RuntimeError.
- Lenguaje dominante
- Python
- Estrellas
- 34.7k
- Forks
- 4.3k
- Merge medio
- 3 h 23 min
- PR fusionados (30 d)
- 3
Preparar el entorno
- Incluye un Dockerfile o un archivo de Docker Compose
- Sin plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de OpenBMB/ChatDev
-
FunctionManager reload can execute stale bytecode after same-size source editsPosiblemente ocupada @jadelike-wine la tomó hace 18 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
CodeReviewModification never shows the programmer the six review regulations, so fixes are made blind to the checklist they are re-reviewed againstPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 1/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 1-2 días Aptitud para principiantes 8/100
Los mantenedores suelen responder en 1 día
Todos los issues de OpenBMB/ChatDev
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
FinanceFlash/unvibecode#509 ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
FAIRmat-NFDI/nomad-distro-dev#161 ·
-
bug v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
modelcontextprotocol/python-sdk#3821 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug severity:low
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 2 días
-
Log-odds calibration uses upper middle score instead of median for even candidate setsPosiblemente ocupada @flysheep-ai la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día