fix(runtime): avoid RuntimeError when calling get_tool_specs with MCP tools from running event loop
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 74/100
Direzione di ricerca
Inizia da runtime/node/agent/tool/tool_manager.py, dove si trovano le due chiamate asyncio.run() vicino alle righe 198-204 (il recupero MCP via HTTP) e alla riga 227 (il recupero MCP via stdio), raggiunte da get_tool_specs. Riproduci il problema con l'esempio minimo nell'issue, chiamando get_tool_specs dall'interno di una funzione async. È completato quando quella chiamata restituisce le specifiche degli strumenti MCP senza un RuntimeError, sia al di fuori sia all'interno di un event loop in esecuzione.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Python
- Stelle
- 34.7k
- Fork
- 4.3k
- Merge medio
- 3h 23m
- PR unite (30g)
- 3
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di OpenBMB/ChatDev
-
FunctionManager reload can execute stale bytecode after same-size source editsForse già presa @jadelike-wine l’ha presa 18 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
CodeReviewModification never shows the programmer the six review regulations, so fixes are made blind to the checklist they are re-reviewed againstForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 1/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 1-2 giorni Idoneità per principianti 8/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di OpenBMB/ChatDev
Issue simili
-
docs: Release playbookApertadocumentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
awslabs/social-messaging-agent-runtime-toolkit#140 ·
I maintainer di solito rispondono entro 1 giorno
-
subtask
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
fga-eps-mds/2026.2-MeasureSoftGram-DOC#187 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
txn2/mcp-data-platform#2493 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
YO-WHATS-UP2/minigit#78 ·
I maintainer di solito rispondono entro 3 giorni
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
liberation-data/drivine-python#432 ·
I maintainer di solito rispondono entro 1 giorno