Skip to content

fix(mcp): ensure MCP discovery completes before agent build in non-interactive sessions - #75933

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/mcp-discovery-timing
Aug 1, 2026
Merged

fix(mcp): ensure MCP discovery completes before agent build in non-interactive sessions#75933
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/mcp-discovery-timing

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Non-interactive sessions (hermes chat -q, hermes -z) now wait for MCP discovery to complete before building the agent, so configured MCP tools are visible in the one and only model turn instead of silently dropped.

Root cause: wait_for_mcp_discovery() only joins an already-created discovery thread — it no-ops if a direct/single-query path reaches agent construction before MCP startup created that thread. oneshot._run_agent() didn't call it at all. Interactive sessions recovered via between-turns late-binding refresh, but one-shot sessions have no second turn.

Changes

  • hermes_cli/mcp_startup.py: New ensure_mcp_discovery_before_agent_build() helper — idempotently starts discovery if needed + bounded wait. Added single_query param to _resolve_discovery_timeout() and wait_for_mcp_discovery() to use the larger mcp_single_query_discovery_timeout bound (default 15s) for one-shot sessions.
  • hermes_cli/config_defaults.py: New mcp_single_query_discovery_timeout config key (default 15.0s). Interactive mcp_discovery_timeout unchanged at 1.5s.
  • hermes_cli/cli_agent_setup_mixin.py: _init_agent calls ensure_mcp_discovery_before_agent_build() instead of bare wait_for_mcp_discovery(), forwarding single_query from _single_query_mode.
  • cli.py: Sets cli._single_query_mode = True in the single-query (-q/-z) path.
  • hermes_cli/oneshot.py: _run_agent calls ensure_mcp_discovery_before_agent_build(single_query=True) before AIAgent construction.
  • tests/hermes_cli/test_mcp_discovery_timing.py: 12 regression tests covering timeout resolution, helper behavior, ordering, bounded wait, and single_query flag forwarding.

Why two timeout bounds

Interactive sessions keep 1.5s because the between-turns late-binding refresh (agent/turn_context.py) picks up late-arriving MCP tools on the next turn. Single-query sessions have ONE turn — no refresh — so 15s gives slow cold-start servers (npx, uvx, remote HTTP) a chance to land. thread.join(timeout) returns the instant discovery completes, so fast servers pay ~0s regardless.

Validation

Before After
hermes -z "use mcp__server__tool" MCP tool invisible if server >1.5s Waits up to 15s for discovery
hermes chat -q "prompt" Same race Same fix via _single_query_mode
Interactive hermes chat 1.5s bound Unchanged (1.5s + between-turns refresh)
No MCP servers configured ~0s startup ~0s (config gate skips discovery)

12 new tests pass. 10 existing MCP startup/refresh tests pass. E2E verified with real imports.

Credits

Composite salvage of 5 community PRs, each of which identified the same bug class from a different entry point:

Closes #38448
Closes #51316
Closes #37013
Closes #68137

…teractive sessions

Non-interactive sessions (hermes chat -q, hermes -z) snapshot the tool
registry at AIAgent construction time. If background MCP discovery hasn't
finished, MCP tools are invisible for the entire session — and unlike
interactive mode, there is no between-turns late-binding refresh to recover.

Root cause: wait_for_mcp_discovery() only joins an already-created discovery
thread, so it no-ops if a direct/single-query path reaches agent construction
before MCP startup created that thread. Oneshot._run_agent() didn't call it
at all.

Fix:
- Add ensure_mcp_discovery_before_agent_build() helper to mcp_startup.py:
  idempotently starts discovery if needed + bounded wait. Fail-open on errors.
- Add single_query parameter to _resolve_discovery_timeout/wait_for_mcp_discovery:
  uses mcp_single_query_discovery_timeout (default 15s) instead of the
  interactive mcp_discovery_timeout (1.5s) because one-shot sessions have no
  second turn to recover.
- Wire into CLI _init_agent (single_query from _single_query_mode flag set
  in cli.py's single-query path) and oneshot._run_agent (single_query=True).
- Interactive sessions unchanged: keep 1.5s bound (between-turns refresh covers).

Closes NousResearch#38448, NousResearch#51316, NousResearch#37013, NousResearch#68137
Composite salvage of NousResearch#60017 (chrishart0), NousResearch#51322 (Bartok9), NousResearch#38620 (buptwz),
NousResearch#43544 (halonke), NousResearch#36882 (vanhoof).
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) August 1, 2026 06:57
@kshitijk4poor
kshitijk4poor merged commit 3572d4b into NousResearch:main Aug 1, 2026
38 checks passed
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard tool/mcp MCP client and OAuth area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 1, 2026
@teknium1 teknium1 added the area/sessions Session lifecycle, resume, persistence, history label Aug 1, 2026
@kshitijk4poor
kshitijk4poor deleted the salvage/mcp-discovery-timing branch August 5, 2026 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

3 participants