fix(mcp): move discovery out of model_tools import side effect (#16856) (#16899)

model_tools.py ran discover_mcp_tools() as a module-level side effect.
discover_mcp_tools() uses a blocking 120s wait internally (via
_run_on_mcp_loop -> future.result(timeout=120)).

The gateway lazy-imports run_agent -> model_tools on the first user
message, which happens inside the asyncio event loop thread.  A slow or
unreachable MCP server therefore froze Discord shard heartbeats and
Telegram polling for up to 120s on the first message after gateway
start.

Fix: remove the module-level call.  Every entry point now runs
discovery explicitly at its own startup, using the context-appropriate
blocking/non-blocking pattern:

- gateway/run.py:       loop.run_in_executor(None, discover_mcp_tools)
                        before platforms start accepting traffic
- hermes_cli/main.py:   inline (no event loop at CLI startup)
- tui_gateway/entry.py: inline (sync stdin loop, no event loop)
- acp_adapter/entry.py: inline before asyncio.run()

Closes #16856.
This commit is contained in:
Teknium
2026-04-28 01:17:58 -07:00
committed by GitHub
parent c8ef786926
commit dd789a4fdf
5 changed files with 58 additions and 6 deletions

View File

@ -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: