Skip to content

fix(tui): start MCP discovery for websocket sessions (#38945) - #54161

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-19304b44
Jun 28, 2026
Merged

fix(tui): start MCP discovery for websocket sessions (#38945)#54161
teknium1 merged 2 commits into
mainfrom
hermes/hermes-19304b44

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

MCP tools now appear in Desktop/dashboard chat sessions on connect, matching the CLI — no manual /reload-mcp, no loss on restart.

Root cause: The desktop app and dashboard chat reach the agent through the /api/ws JSON-RPC sidecar (tui_gateway.ws.handle_ws), not through tui_gateway.entry.main() — the stdio-TUI path that spawns the background MCP discovery thread. In the WS process discovery was therefore never started: _make_agent only waits (wait_for_mcp_discovery), which no-ops when the thread was never created, so the agent snapshotted an MCP-less tool list. The only discovery trigger reachable there was a manual /reload-mcp, which is why tools showed up after a reload but vanished on every restart.

Changes

  • tui_gateway/ws.py: start the shared, idempotent, config-gated background MCP discovery in handle_ws() right after accept() and before gateway.ready, so the first agent build picks up already-spawning servers (the existing late-binding refresh handles slow ones).
  • tests/test_tui_gateway_ws.py: regression test asserting handle_ws invokes start_background_mcp_discovery(thread_name="tui-ws-mcp-discovery").

Validation

Before After
WS session (desktop/dashboard) discovery never starts → 0 MCP tools discovery starts on connect → MCP tools live
Non-MCP users ~0s (config-gated probe, no thread)
Targeted tests tests/test_tui_gateway_ws.py 4/4 pass
E2E (real handle_ws + real mcp_startup, MCP server in config) discover_mcp_tools never called thread spawned, discover_mcp_tools called

Salvaged from #38301 (@sweetcornna, earliest of the cluster). Only the ws.py hunk remained relevant — the rest of #38301 (migrating the thread slot entry.pymcp_startup.py) already landed on main. Duplicate #42794 (@LionGateOS) fixed the same site.

Fixes #38945.

Infographic

infographic

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-19304b44 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11656 on HEAD, 11656 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 6119 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM (approve-ready, COMMENT due to token permissions)

Fix for #38945: starts background MCP discovery for websocket sessions. The desktop app and dashboard chat reach the agent through the WS sidecar, not through the stdio TUI path — without this, MCP tools are never discovered.

Changes

  • handle_ws() now calls start_background_mcp_discovery() with a dedicated thread name
  • 1 test verifying the call is made with correct kwargs

Looks Good

  • Idempotent, config-gated — safe to call multiple times
  • The comment explains the architectural reason clearly
  • One concern: the test creates a real asyncio.run() call which could leak, but the try/finally cleanup handles it

Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) tool/mcp MCP client and OAuth labels Jun 28, 2026
sweetcornna and others added 2 commits June 28, 2026 04:01
The desktop app and dashboard chat reach the agent through the /api/ws
JSON-RPC sidecar (tui_gateway.ws.handle_ws), NOT through
tui_gateway.entry.main() — the stdio-TUI path that spawns the background
MCP discovery thread. In the WS process discovery was therefore never
started: _make_agent only *waits* (wait_for_mcp_discovery), which no-ops
when the thread was never created, so the agent snapshotted an MCP-less
tool list. The only discovery trigger reachable was a manual /reload-mcp,
which is why tools appeared after a reload but vanished on restart.

Start the shared, idempotent, config-gated background discovery in
handle_ws right after accept() and before gateway.ready, so the first
agent build picks up already-spawning servers (and the existing
late-binding refresh handles slow ones).

Fixes #38945.
@teknium1
teknium1 force-pushed the hermes/hermes-19304b44 branch from 55b92ad to ff2dcb8 Compare June 28, 2026 11:01

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary\n\nVerdict: LGTM\n\nStarts MCP discovery for websocket sessions. Without this, the desktop app and dashboard chat never run discovery, so the agent snapshots an MCP-less tool list. Clean implementation with idempotent start. Regression test for #38945.\n\n---\nReviewed by Hermes Agent

@teknium1
teknium1 merged commit f22b9d3 into main Jun 28, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-19304b44 branch June 28, 2026 11:14
@karlesnine

Copy link
Copy Markdown

Profile-blind MCP discovery: start_background_mcp_discovery() ignores ?profile= parameter

Environment: Hermes v0.19.0 (2026.7.20), this fix is applied. Remote dashboard with 5 profiles.

Observed behavior:
After this fix, MCP discovery runs for WebSocket sessions, but it always discovers MCP servers from the dashboard's profile (default), ignoring the ?profile=<name> query parameter passed to /api/ws or /api/pty.

Root cause:
In tui_gateway/ws.py line 317, start_background_mcp_discovery() is called in the dashboard process context (before the PTY subprocess is spawned). This function calls _has_configured_mcp_servers() which reads config.yaml via read_raw_config() — using the dashboard's HERMES_HOME (profile default), not the target profile's config.

The PTY subprocess spawned later (via _resolve_chat_argv) correctly sets HERMES_HOME to the target profile, but the MCP discovery thread has already started (or not started) based on the dashboard's config.

Reproduction:

  1. Configure bluesky_sheepyr MCP server in profile sheepyr config.yaml
  2. Configure bluesky_karlesnine MCP server in profile default config.yaml
  3. Connect to dashboard with ?profile=sheepyr
  4. MCP tools from bluesky_karlesnine (default) are exposed, NOT bluesky_sheepyr
  5. hermes -p sheepyr mcp test bluesky_sheepyr confirms the server works (21 tools, <3s connect)

Expected fix: Pass the profile parameter through to start_background_mcp_discovery() so it reads the correct config.yaml, or defer MCP discovery to the PTY subprocess which has the correct HERMES_HOME.

Related: #37713 (Desktop doesn't pass ?profile= at all — compound bug), #30626 (gateway profile-blindness).

@karlesnine

Copy link
Copy Markdown

This fix is incomplete. The MCP discovery still reads from the default profile because the Desktop doesn't transmit the active profile - tracked in #71527. Even with this PR merged, per-profile MCP servers (e.g. bluesky_sheepyr) are not discovered in Desktop WebSocket sessions. The actual root cause is the profile routing gap in #71527.

@LionGateOS

LionGateOS commented Jul 26, 2026 via email

Copy link
Copy Markdown
Contributor

@LionGateOS

LionGateOS commented Jul 26, 2026 via email

Copy link
Copy Markdown
Contributor

@karlesnine

Copy link
Copy Markdown

@LionGateOS thanks for the follow-up and for pushing the profile-scoped version forward.

Two notes so we don't diverge:

  1. The companion PR you rebased (fix(tui-gateway): scope MCP discovery to active profile #72135 "scope MCP discovery to active profile") is now closed, not merged. Could you confirm what superseded it — and whether your remaining "scope live session lookup by profile" PR fix(tui-gateway): scope live session lookup by profile #72295 (which AI-triage flagged as a duplicate of fix(tui-gateway): negotiate and isolate profile sessions #64266) picks up the discovery-side fix, or only the session-lookup path? If only the latter, the discovery path still has no merged fix.

  2. Independently, I posted a root-cause analysis on [Bug]: Desktop does not pass active profile as ?profile= query param to /api/ws WebSocket #71527 yesterday (issuecomment-5091933021): the profile-blind behavior is not only a routing gap — it is also caused by a module-global singleton _mcp_discovery_started in hermes_cli/mcp_startup.py:37-41. Once the first profile started its discovery thread, every subsequent profile short-circuits (_mcp_discovery_started is True and the default thread is alive), so per-profile servers like bluesky_sheepyr never get discovered — even when set_hermes_home_override(profile_home) runs before ensure_mcp_discovery_started(). The CLI is unaffected because each invocation is a fresh process with no stale singleton.

If your profile-scoped fix (or #64266) doesn't touch that singleton, the per-profile MCP discovery will still be blind in Desktop websocket sessions. Worth aligning the two threads.

@LionGateOS

Copy link
Copy Markdown
Contributor

Thanks for the careful follow-up.

To clarify: #72135 was closed because the MCP discovery work was carried forward through #72219, per Teknium's note that the commits were cherry-picked onto current main with authorship preserved.

#72295 is separate. It only covers the live-session lookup/reuse path by scoping with . It does not pick up the MCP discovery-side fix, and it does not address the singleton behavior.

I agree those should not be conflated. I'll treat the remaining per-profile MCP discovery singleton issue as a separate follow-up area and first check whether #72219, #64266, or another existing branch already covers it before proposing anything new.

@LionGateOS

Copy link
Copy Markdown
Contributor

Small correction to my previous comment: the stripped code identifiers were _find_live_session_by_key, profile_home, and _mcp_discovery_started.

Restated: #72295 only covers the live-session lookup/reuse path by scoping _find_live_session_by_key with profile_home. It does not pick up the MCP discovery-side fix, and it does not address the _mcp_discovery_started singleton behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop/TUI sessions do not reliably expose enabled MCP tools (Todoist)

6 participants