fix(mcp): move discovery out of model_tools import side effect (#16856)#16899
Merged
Conversation
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.
cluricaun28
referenced
this pull request
in cluricaun28/Logos
Apr 28, 2026
…6) (#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.
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
…esearch#16856) (NousResearch#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 NousResearch#16856.
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…esearch#16856) (NousResearch#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 NousResearch#16856.
dannyJ848
pushed a commit
to dannyJ848/hermes-agent
that referenced
this pull request
May 17, 2026
…esearch#16856) (NousResearch#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 NousResearch#16856.
This was referenced May 30, 2026
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…esearch#16856) (NousResearch#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 NousResearch#16856.
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
…esearch#16856) (NousResearch#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 NousResearch#16856.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes the module-level
discover_mcp_tools()call inmodel_tools.pyso lazy-importing this module from inside an asyncio event loop no longer blocks the loop for up to 120s when an MCP server is slow or unreachable. Closes #16856. Supersedes #16877 (same intent, cleaner fix per suggestion #1 in the issue — credit @Bartok9).Root cause
model_tools.pyrandiscover_mcp_tools()as an import-time side effect. The gateway lazy-importsrun_agent(→model_tools) on the first user message, which executes inside the asyncio event loop thread.discover_mcp_tools()usesfuture.result(timeout=120)internally, freezing Discord shard heartbeats and Telegram polling for up to 120s whenever a configured MCP server was unreachable.Changes
model_tools.py: remove module-leveldiscover_mcp_tools()call (keeps the symbol importable for explicit callers).gateway/run.py: run discovery vialoop.run_in_executor(None, discover_mcp_tools)instart_gateway()beforerunner.start()— loop stays responsive, tools are ready before the first message arrives.hermes_cli/main.py: inline discovery in the agent-command startup path (no event loop running).tui_gateway/entry.py: inline discovery inmain()(sync stdin loop).acp_adapter/entry.py: inline discovery beforeasyncio.run()(sync context).Why this instead of PR #16877
#16877 added event-loop detection inside
model_tools.pyand offloaded discovery to an executor when a loop was running. That worked, but the module-level call made the first gateway message race against discovery (first message could hit the model before MCP tool schemas were registered). Moving the call into each entry point's startup sequence avoids both problems: no import-time side effect, and gateway discovery completes before platforms accept traffic.Validation
import model_toolstriggers MCP discoverytests/test_model_tools_async_bridge.pytests/tools/test_mcp_tool.pyHow to verify manually
config.yaml.