Defer MCP discovery away from model_tools import blocking event loop
Cluster 16856. Data as of
Nothing open on GitHub. Fix merged in #16899 on 28 Apr 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #16899. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #16899. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Three members relate to issue #16856: a report that lazily importing model_tools can block the asyncio event loop on the first gateway message when an MCP server is slow or unreachable, and two pull requests that propose moving or deferring MCP discovery out of the import path. The titles show the intended approach but do not indicate outcomes.
Open issues0
Open PRs0
Fixes on record
-
#16899 fix(mcp): move discovery out of model_tools import side effect (#16856)
files:
acp_adapter/entry.py,gateway/run.py,hermes_cli/main.py,model_tools.py,tui_gateway/entry.pyShow the diff
-
acp_adapter/entry.py@@ -112,6 +112,17 @@ def main() -> None: import acp from .server import HermesACPAgent + # MCP tool discovery from config.yaml — run before asyncio.run() so + # it's safe to use blocking waits. (ACP also registers per-session + # MCP servers dynamically via asyncio.to_thread inside the event + # loop; that path is unaffected.) Moved from model_tools.py module + # scope to avoid freezing the gateway's loop on lazy import (#16856). + try: + from tools.mcp_tool import discover_mcp_tools + discover_mcp_tools() + except Exception: + logger.debug("MCP tool discovery failed at ACP startup", exc_info=True) + agent = HermesACPAgent() try: asyncio.run(acp.run_agent(agent, use_unstable_protocol=True)) -
gateway/run.py@@ -11671,6 +11671,19 @@ def restart_signal_handler(): atexit.register(remove_pid_file) atexit.register(release_gateway_runtime_lock) + # MCP tool discovery — run in an executor so the asyncio event loop + # stays responsive even when a configured MCP server is slow or + # unreachable. discover_mcp_tools() uses a blocking 120s wait + # internally; calling it from the loop thread would freeze platform + # heartbeats (Discord shard, Telegram polling) until it returned. + # See #16856. + try: + from tools.mcp_tool import discover_mcp_tools + _loop = asyncio.get_running_loop() + await _loop.run_in_executor(None, discover_mcp_tools) + except Exception as e: + logger.debug("MCP tool discovery failed: %s", e) + # Start the gateway success = await runner.start() if not success: -
hermes_cli/main.py@@ -10101,6 +10101,17 @@ def cmd_acp(args): logger.debug( "plugin discovery failed at CLI startup", exc_info=True, ) + try: + # MCP tool discovery — no event loop running in CLI/TUI startup, + # so inline is safe. Moved here from model_tools.py module scope + # to avoid freezing the gateway's event loop on its first message + # via the same lazy import path (#16856). + from tools.mcp_tool import discover_mcp_tools + discover_mcp_tools() + except Exception: + logger.debug( + "MCP tool discovery failed at CLI startup", exc_info=True, + ) try: from hermes_cli.config import load_config from agent.shell_hooks import register_from_config -
model_tools.py@@ -138,12 +138,18 @@ def _run_async(coro): discover_builtin_tools() -# MCP tool discovery (external MCP servers from config) -try: - from tools.mcp_tool import discover_mcp_tools - discover_mcp_tools() -except Exception as e: - logger.debug("MCP tool discovery failed: %s", e) +# MCP tool discovery (external MCP servers from config) used to run here as +# a module-level side effect. It was removed because discover_mcp_tools() +# internally uses a blocking future.result(timeout=120) wait, and the +# gateway lazy-imports this module from inside the asyncio event loop on +# the first user message — freezing Discord/Telegram heartbeats for up to +# 120s whenever any configured MCP server was slow or unreachable (#16856). +# +# Each entry point now runs discovery explicitly at its own startup: +# - gateway/run.py -> start_gateway() uses run_in_executor +# - cli.py, hermes_cli/* -> inline on startup (no event loop) +# - tui_gateway/server.py -> inline on startup (no event loop) +# - acp_adapter/server.py -> asyncio.to_thread on session init # Plugin tool discovery (user/project/pip plugins) try: -
tui_gateway/entry.py@@ -105,6 +105,17 @@ def _log_exit(reason: str) -> None: def main(): _install_sidecar_publisher() + # MCP tool discovery — inline is safe here: TUI entry is a plain + # sync loop with no asyncio event loop to block. Previously ran as + # a model_tools.py module-level side effect; moved to explicit + # startup calls to avoid freezing the gateway's loop on lazy import + # (#16856). + try: + from tools.mcp_tool import discover_mcp_tools + discover_mcp_tools() + except Exception: + pass + if not write_json({ "jsonrpc": "2.0", "method": "event",
-
1 PR closed without merging
From the close comments: other 1
-
#16877 fix(gateway): defer MCP discovery to executor when imported inside event loop (#16856) · other →
#1,#16899
Closed issues · 1
- Closed#16856Lazy import of model_tools blocks asyncio event loop on first gateway message when an MCP server is slow/unreachable
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Merged PRs
- #16899
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 28 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.