NousResearch/hermes-agent

MCP tool discovery missing in Desktop/TUI/Dashboard sessions

Cluster 37589 · apps/desktop. Data as of

3 open items. Fix merged in #122861 on 25 Sep 2026. Check it covers your case.

The short answer

0open issues
3open PRs
18items grouped
Known?
Yes. 18 related items grouped here, 7 issues already closed.
Fix event
A fix was merged to main in PR #122861. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #54161, #61949, #122861. That is separate from the fix event.
Still open
0 issues and 3 PRs.

Most members concern MCP tools not appearing in Desktop, TUI, or Dashboard sessions, with related changes to start or wait for MCP discovery and to rebuild tool schemas. A smaller subset covers resolving uv/uvx commands under GUI-style PATHs, which is an adjacent issue.

Open issues0

No open issues.

Open PRs3

  • PR#37665fix(tools): resolve uv/uvx MCP commands under GUI-style PATHs (fixes #37589)
  • PR#67125fix(mcp): resolve uv and uvx under filtered PATH
  • PR#107205fix(mcp): increase discovery timeout and sync-wait in TUI agent build

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #44512 fix(desktop): load MCP tools for dashboard /api/ws backends

    merged to main

    files: hermes_cli/main.py, tests/hermes_cli/test_dashboard_unified_launch.py, tests/test_tui_gateway_server.py, tui_gateway/server.py

    Show the diff
    • hermes_cli/main.py
      @@ -10431,6 +10431,26 @@ def cmd_dashboard(args):
               # the missing-provider state if it matters.
               print(f"⚠ Plugin discovery failed: {exc}", file=sys.stderr)
       
      +    # Desktop chat uses the dashboard's in-process /api/ws gateway, which builds
      +    # agents via tui_gateway.server._make_agent.  That path only snapshots the
      +    # tool registry — it never starts MCP discovery (the stdio TUI does that in
      +    # tui_gateway/entry.py, which the dashboard process doesn't run).  Without
      +    # this, a profile's configured MCP servers never connect, so desktop
      +    # sessions show no MCP tools.  Spawn discovery in the background here so a
      +    # slow/dead server can't block dashboard startup.
      +    try:
      +        from hermes_cli.mcp_startup import start_background_mcp_discovery
      +
      +        start_background_mcp_discovery(
      +            logger=logger,
      +            thread_name="dashboard-mcp-discovery",
      +        )
      +    except Exception:
      +        logger.debug(
      +            "Background MCP tool discovery failed at dashboard startup",
      +            exc_info=True,
      +        )
      +
           from hermes_cli.web_server import start_server
       
           # The in-browser Chat tab (the embedded TUI over PTY/WebSocket) is always
    • tests/hermes_cli/test_dashboard_unified_launch.py
      @@ -128,3 +128,45 @@ def test_reexec_child_does_not_reroute(self, main_mod, monkeypatch):
               with pytest.raises((SystemExit, AttributeError, ImportError, TypeError)):
                   main_mod.cmd_dashboard(_args(open_profile="worker_x"))
               assert execs == []
      +
      +    def test_dashboard_starts_mcp_discovery_for_ws_backend(self, main_mod, monkeypatch):
      +        """The dashboard process serves the /api/ws gateway but never runs
      +        tui_gateway/entry.py, so it must kick off MCP discovery itself or
      +        desktop sessions never see a profile's MCP tools."""
      +        monkeypatch.setattr(
      +            "hermes_cli.profiles.get_active_profile_name", lambda: "default"
      +        )
      +        monkeypatch.delenv("HERMES_WEB_DIST", raising=False)
      +        monkeypatch.setattr(main_mod, "_sync_bundled_skills_quietly", lambda: None)
      +        monkeypatch.setattr(main_mod, "_build_web_ui", lambda *_a, **_k: True)
      +        monkeypatch.setitem(sys.modules, "fastapi", types.SimpleNamespace())
      +        monkeypatch.setitem(sys.modules, "uvicorn", types.SimpleNamespace())
      +        monkeypatch.setitem(
      +            sys.modules,
      +            "hermes_logging",
      +            types.SimpleNamespace(setup_logging=lambda **_k: None),
      +        )
      +        monkeypatch.setitem(
      +            sys.modules,
      +            "hermes_cli.plugins",
      +            types.SimpleNamespace(discover_plugins=lambda: None),
      +        )
      +        calls = []
      +        monkeypatch.setattr(
      +            "hermes_cli.mcp_startup.start_background_mcp_discovery",
      +            lambda **kwargs: calls.append(kwargs),
      +        )
      +        monkeypatch.setitem(
      +            sys.modules,
      +            "hermes_cli.web_server",
      +            types.SimpleNamespace(start_server=lambda **_kwargs: None),
      +        )
      +
      +        main_mod.cmd_dashboard(_args())
      +
      +        assert calls == [
      +            {
      +                "logger": main_mod.logger,
      +                "thread_name": "dashboard-mcp-discovery",
      +            }
      +        ]
    • tests/test_tui_gateway_server.py
      @@ -6096,6 +6096,24 @@ def test_make_agent_reads_nested_max_turns(monkeypatch):
           assert mock_agent.call_args.kwargs["max_iterations"] == 200
       
       
      +def test_make_agent_waits_for_shared_mcp_discovery(monkeypatch):
      +    _setup_make_agent_mocks(monkeypatch, {})
      +    waited = []
      +
      +    from hermes_cli import mcp_startup
      +
      +    monkeypatch.setattr(
      +        mcp_startup,
      +        "wait_for_mcp_discovery",
      +        lambda timeout=0.75: waited.append(timeout),
      +    )
      +
      +    with patch("run_agent.AIAgent"):
      +        server._make_agent("sid1", "key1")
      +
      +    assert waited == [0.75]
      +
      +
       def test_make_agent_nested_max_turns_takes_priority(monkeypatch):
           _setup_make_agent_mocks(
               monkeypatch, {"agent": {"max_turns": 500}, "max_turns": 100}
    • tui_gateway/server.py
      @@ -3072,11 +3072,17 @@ def _make_agent(
           from hermes_cli.runtime_provider import resolve_runtime_provider
       
           # MCP tool discovery runs in a background daemon thread at startup so a
      -    # dead server can't freeze the shell (see tui_gateway/entry.py).  The agent
      -    # snapshots its tool list once here and never re-reads it, so briefly wait
      -    # for in-flight discovery to land before building — bounded, so a slow/dead
      -    # server still can't block.  No-op once discovery has finished (every build
      -    # after the first during a slow startup).
      +    # dead server can't freeze the shell.  The agent snapshots its tool list
      +    # once here and never re-reads it, so briefly wait for in-flight discovery
      +    # to land before building — bounded, so a slow/dead server still can't
      +    # block. Dashboard /api/ws uses hermes_cli.mcp_startup; TUI stdio keeps
      +    # its existing tui_gateway.entry-owned thread.
      +    try:
      +        from hermes_cli.mcp_startup import wait_for_mcp_discovery
      +
      +        wait_for_mcp_discovery()
      +    except Exception:
      +        pass
           try:
               from tui_gateway.entry import wait_for_mcp_discovery
       
  • #54161 fix(tui): start MCP discovery for websocket sessions (#38945)

    merged to main

    files: infographic/mcp-ws-discovery/infographic.png, tests/test_tui_gateway_ws.py, tui_gateway/ws.py

  • #61949 fix(tui): discover MCP tools in slash workers

    merged to main

    files: tests/tui_gateway/test_slash_worker_mcp_discovery.py, tui_gateway/slash_worker.py

    Show the diff
    • tests/tui_gateway/test_slash_worker_mcp_discovery.py
      @@ -0,0 +1,105 @@
      +"""Integration coverage for profile-local MCP discovery in slash workers."""
      +
      +from __future__ import annotations
      +
      +import json
      +import os
      +from pathlib import Path
      +import queue
      +import subprocess
      +import sys
      +import textwrap
      +import threading
      +
      +import pytest
      +import yaml
      +
      +pytest.importorskip("mcp.server.fastmcp")
      +
      +
      +def test_profile_local_mcp_tool_is_visible_in_slash_worker(tmp_path):
      +    profile_home = tmp_path / "profile-home"
      +    profile_home.mkdir()
      +    marker = "profile-local-61922"
      +    server = tmp_path / "fastmcp_probe.py"
      +    server.write_text(
      +        textwrap.dedent(
      +            f"""
      +            from mcp.server.fastmcp import FastMCP
      +
      +            mcp = FastMCP("profileprobe")
      +
      +            @mcp.tool()
      +            def hermes_61922_profile_probe() -> str:
      +                return {marker!r}
      +
      +            if __name__ == "__main__":
      +                mcp.run(transport="stdio")
      +            """
      +        ),
      +        encoding="utf-8",
      +    )
      +    (profile_home / "config.yaml").write_text(
      +        yaml.safe_dump(
      +            {
      +                "mcp_servers": {
      +                    "profileprobe": {
      +                        "enabled": True,
      +                        "command": sys.executable,
      +                        "args": [str(server)],
      +                    }
      +                }
      +            }
      +        ),
      +        encoding="utf-8",
      +    )
      +
      +    env = os.environ.copy()
      +    for key in list(env):
      +        if key.endswith("_API_KEY") or key.endswith("_TOKEN"):
      +            env.pop(key)
      +    env["HERMES_HOME"] = str(profile_home)
      +    env["PYTHONPATH"] = str(Path(__file__).resolve().parents[2])
      +    env["HERMES_SLASH_WATCHDOG_GRACE_S"] = "0"
      +    env["HERMES_SLASH_WATCHDOG_POLL_S"] = "0.05"
      +    proc = subprocess.Popen(
      +        [
      +            sys.executable,
      +            "-u",
      +            "-m",
      +            "tui_gateway.slash_worker",
      +            "--session-key",
      +            "agent:main:tui:dm:mcp-profile-test",
      +        ],
      +        stdin=subprocess.PIPE,
      +        stdout=subprocess.PIPE,
      +        stderr=subprocess.PIPE,
      +        text=True,
      +        env=env,
      +        cwd=tmp_path,
      +    )
      +    output: queue.Queue[str] = queue.Queue()
      +    try:
      +        assert proc.stdin is not None
      +        assert proc.stdout is not None
      +        stdout = proc.stdout
      +        threading.Thread(
      +            target=lambda: output.put(stdout.readline()),
      +            daemon=True,
      +        ).start()
      +        proc.stdin.write(json.dumps({"id": 1, "command": "/tools"}) + "\n")
      +        proc.stdin.flush()
      +        try:
      +            line = output.get(timeout=10)
      +        except queue.Empty:
      +            pytest.fail("slash worker produced no /tools response within 10 seconds")
      +        response = json.loads(line)
      +        assert response["ok"] is True
      +        assert "mcp__profileprobe__hermes_61922_profile_probe" in response["output"]
      +    finally:
      +        proc.terminate()
      +        try:
      +            proc.wait(timeout=5)
      +        except subprocess.TimeoutExpired:
      +            proc.kill()
      +            proc.wait(timeout=5)
    • tui_gateway/slash_worker.py
      @@ -65,6 +65,27 @@ def _is_orphaned(original_ppid, parent_create_time, getppid=os.getppid) -> bool:
               return True
       
       
      +def _prepare_slash_worker_runtime() -> None:
      +    """Start bounded MCP discovery before HermesCLI snapshots tools.
      +
      +    Each slash_worker child is its own process — the parent ``hermes serve``
      +    discovery thread does not populate this registry (issue #61891).
      +    """
      +    import logging
      +
      +    from hermes_cli.mcp_startup import (
      +        start_background_mcp_discovery,
      +        wait_for_mcp_discovery,
      +    )
      +
      +    logger = logging.getLogger(__name__)
      +    start_background_mcp_discovery(
      +        logger=logger,
      +        thread_name="slash-worker-mcp-discovery",
      +    )
      +    wait_for_mcp_discovery()
      +
      +
       def _start_parent_death_watchdog(original_ppid, parent_create_time) -> None:
           def _loop():
               while not _is_orphaned(original_ppid, parent_create_time):
      @@ -129,6 +150,7 @@ def main():
           except psutil.Error:
               parent_create_time = 0.0
           _start_parent_death_watchdog(orig_ppid, parent_create_time)
      +    _prepare_slash_worker_runtime()
       
           with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
               cli = HermesCLI(model=args.model or None, compact=True, resume=args.session_key, verbose=False)
  • #122861 fix(mcp): resolve bare uv/uvx under GUI-style PATHs (#37589)

    merged to main

    files: hermes_platform/resolver/known_dirs.py, tests/tools/test_mcp_tool_issue_948.py, tools/mcp_tool_config.py

4 PRs closed without merging

From the close comments: done elsewhere 2 · superseded by another PR 1 · other 1

  • #39007 fix(tui): rebuild agent tool schema when background MCP discovery completes (fixes #38945) · done elsewhere → 93d6e73
  • #42703 fix(dashboard): start MCP tool discovery for Desktop/Dashboard sessions · done elsewhere → 7396977, #44512, #42694, #42703, #43189, #40407
  • #42797 fix(dashboard): start MCP discovery thread in Dashboard mode (#42694) · superseded by another PR → #42703, #42694
  • #61922 fix(tui): MCP discovery for slash_worker and TUI agent builds (#61891) · other → #61949
Closed issues · 7
  • Closed#37589Desktop sessions miss configured MCP tools and uvx servers can fail under macOS GUI PATH
  • Closed#38271[Bug]: Manual MCP reload needed on Start
  • Closed#38945Desktop/TUI sessions do not reliably expose enabled MCP tools (Todoist)
  • Closed#42694[Bug]: MCP tools not discovered in Desktop/Dashboard mode — start_server() bypasses MCP discovery thread
  • Closed#42726[Bug]: Desktop skips MCP tool discovery — MCP tools not available
  • Closed#51587[Bug]: MCP server tools connect and are enabled, but never surface into the agent's session toolset
  • Closed#61891MCP tools load in CLI sessions but not in desktop/TUI sessions (slash_worker subprocess)
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.09: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means 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 Lab

At a glance

Subsystem
apps/desktop
Merged PRs
#44512, #54161, #61949, #122861
Closed issues
7
Attempts
4 PRs closed without merging
Fix link checked
25 Sep 2026

For your agent

The same facts, typed.