# NousResearch/hermes-agent · MCP tool discovery missing in Desktop/TUI/Dashboard sessions

## Open now

- 0 issues, 3 PRs open on GitHub.
- PR #37665: fix(tools): resolve uv/uvx MCP commands under GUI-style PATHs (fixes #37589)
- PR #67125: fix(mcp): resolve uv and uvx under filtered PATH
- PR #107205: fix(mcp): increase discovery timeout and sync-wait in TUI agent build

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.

## TL;DR

- 18 member(s): 0 open issue(s), 7 closed issue(s), 3 open PR(s), 4 merged PR(s), 4 PR(s) closed without merging.
- A fix was merged to main in PR #122861. Verify it addresses your case.
- **Recommended:** read PR #122861 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-54161,pr-61949,pr-122861`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Closed / duplicate issues

- #37589: Desktop sessions miss configured MCP tools and uvx servers can fail under macOS GUI PATH
- #38271: \[Bug\]: Manual MCP reload needed on Start
- #38945: Desktop/TUI sessions do not reliably expose enabled MCP tools (Todoist)
- #42694: \[Bug\]: MCP tools not discovered in Desktop/Dashboard mode — start_server() bypasses MCP discovery thread
- #42726: \[Bug\]: Desktop  skips MCP tool discovery — MCP tools not available
- #51587: \[Bug\]: MCP server tools connect and are enabled, but never surface into the agent's session toolset
- #61891: MCP tools load in CLI sessions but not in desktop/TUI sessions (slash_worker subprocess)

## Prior & competing PRs

- **#39007: fix(tui): rebuild agent tool schema when background MCP discovery completes (fixes #38945)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `93d6e730288e4ffab8076a0539f25e37a71f238f`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#42797: fix(dashboard): start MCP discovery thread in Dashboard mode (#42694)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#42703`, `#42694`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#44512: fix(desktop): load MCP tools for dashboard /api/ws backends**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/main.py`, `tests/hermes_cli/test_dashboard_unified_launch.py`, `tests/test_tui_gateway_server.py`, `tui_gateway/server.py`
  - fix hunks:
  - `hermes_cli/main.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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 · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `infographic/mcp-ws-discovery/infographic.png`, `tests/test_tui_gateway_ws.py`, `tui_gateway/ws.py`
- **#61922: fix(tui): MCP discovery for slash_worker and TUI agent builds (#61891)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#61949`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#61949: fix(tui): discover MCP tools in slash workers**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `tests/tui_gateway/test_slash_worker_mcp_discovery.py`, `tui_gateway/slash_worker.py`
  - fix hunks:
  - `tests/tui_gateway/test_slash_worker_mcp_discovery.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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):
    ```

    ```diff
    @@ -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 · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_platform/resolver/known_dirs.py`, `tests/tools/test_mcp_tool_issue_948.py`, `tools/mcp_tool_config.py`
- **#37665: fix(tools): resolve uv/uvx MCP commands under GUI-style PATHs (fixes #37589)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `f4ce36cd4724`, `#37589`
  - files: `tests/tools/test_mcp_tool_issue_948.py`, `tools/mcp_tool.py`
- **#42703: fix(dashboard): start MCP tool discovery for Desktop/Dashboard sessions**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `73969771a`, `#44512`, `#42694`, `#42703`, `#43189`, `#40407`
- **#67125: fix(mcp): resolve uv and uvx under filtered PATH**. open
  - maintainer direction: category `partial_fix`; implication `needs_llm`; refs `72ed4fb0cf7d`, `#37665`, `#67125`, `#67178`, `#37589`, `#56537`, `#56594`, `#61153`, `#8305`, `#67873`, `#56536`, `#54141`, `#374151`
  - files: `contributors/emails/[redacted:email]`, `tests/tools/test_mcp_tool_issue_948.py`, `tools/mcp_tool.py`
- **#107205: fix(mcp): increase discovery timeout and sync-wait in TUI agent build**. open
  - maintainer direction: category `silent_close`; implication `active`
  - files: `hermes_cli/config_defaults.py`, `tests/tui_gateway/test_mcp_discovery_sync.py`

## Fix status

- Merged to main in PR #122861 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-09-25`.
- Subsystem: `apps/desktop`
