{"repo":"NousResearch/hermes-agent","public_id":"37589","display_label":"MCP tool discovery missing in Desktop/TUI/Dashboard sessions","summary":"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.","subsystem":"apps/desktop","counts":{"members":18,"open_issues":0,"open_issues_distinct":0,"open_prs":3,"closed_issues":7,"merged_prs":4,"attempt_prs":7},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":122861,"merged_at":"2026-09-25T23:31:03+00:00"},"resolution":{"state":"partially_resolved","by":"pr-54161,pr-61949,pr-122861"},"freshness":{"state":"verified","last_verified_at":"2026-09-25T23:31:03+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.08836601307189543},"fix_edges":[{"issue_number":37589,"pr_number":122861,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-09-25T23:31:03+00:00"},{"issue_number":38945,"pr_number":54161,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-28T11:14:12+00:00"},{"issue_number":42694,"pr_number":44512,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-11T22:45:46+00:00"},{"issue_number":61891,"pr_number":61949,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-10T12:39:18+00:00"}],"members":[{"kind":"issue","number":37589,"title":"Desktop sessions miss configured MCP tools and uvx servers can fail under macOS GUI PATH","url":null,"state":"closed"},{"kind":"issue","number":38271,"title":"[Bug]: Manual MCP reload needed on Start","url":null,"state":"closed"},{"kind":"issue","number":38945,"title":"Desktop/TUI sessions do not reliably expose enabled MCP tools (Todoist)","url":null,"state":"closed"},{"kind":"issue","number":42694,"title":"[Bug]: MCP tools not discovered in Desktop/Dashboard mode — start_server() bypasses MCP discovery thread","url":null,"state":"closed"},{"kind":"issue","number":42726,"title":"[Bug]: Desktop  skips MCP tool discovery — MCP tools not available","url":null,"state":"closed"},{"kind":"issue","number":51587,"title":"[Bug]: MCP server tools connect and are enabled, but never surface into the agent's session toolset","url":null,"state":"closed"},{"kind":"issue","number":61891,"title":"MCP tools load in CLI sessions but not in desktop/TUI sessions (slash_worker subprocess)","url":null,"state":"closed"},{"kind":"pr","number":37665,"title":"fix(tools): resolve uv/uvx MCP commands under GUI-style PATHs (fixes #37589)","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"f4ce36cd4724","number":0},{"kind":"pr","value":"#37589","number":37589}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":39007,"title":"fix(tui): rebuild agent tool schema when background MCP discovery completes (fixes #38945)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"93d6e730288e4ffab8076a0539f25e37a71f238f","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":42703,"title":"fix(dashboard): start MCP tool discovery for Desktop/Dashboard sessions","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"73969771a","number":0},{"kind":"pr","value":"#44512","number":44512},{"kind":"pr","value":"#42694","number":42694},{"kind":"pr","value":"#42703","number":42703},{"kind":"pr","value":"#43189","number":43189},{"kind":"pr","value":"#40407","number":40407}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":42797,"title":"fix(dashboard): start MCP discovery thread in Dashboard mode (#42694)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#42703","number":42703},{"kind":"pr","value":"#42694","number":42694}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":44512,"title":"fix(desktop): load MCP tools for dashboard /api/ws backends","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/main.py","tests/hermes_cli/test_dashboard_unified_launch.py","tests/test_tui_gateway_server.py","tui_gateway/server.py"],"fix_hunks":[{"file":"hermes_cli/main.py","hunks":["@@ -10431,6 +10431,26 @@ def cmd_dashboard(args):\n         # the missing-provider state if it matters.\n         print(f\"⚠ Plugin discovery failed: {exc}\", file=sys.stderr)\n \n+    # Desktop chat uses the dashboard's in-process /api/ws gateway, which builds\n+    # agents via tui_gateway.server._make_agent.  That path only snapshots the\n+    # tool registry — it never starts MCP discovery (the stdio TUI does that in\n+    # tui_gateway/entry.py, which the dashboard process doesn't run).  Without\n+    # this, a profile's configured MCP servers never connect, so desktop\n+    # sessions show no MCP tools.  Spawn discovery in the background here so a\n+    # slow/dead server can't block dashboard startup.\n+    try:\n+        from hermes_cli.mcp_startup import start_background_mcp_discovery\n+\n+        start_background_mcp_discovery(\n+            logger=logger,\n+            thread_name=\"dashboard-mcp-discovery\",\n+        )\n+    except Exception:\n+        logger.debug(\n+            \"Background MCP tool discovery failed at dashboard startup\",\n+            exc_info=True,\n+        )\n+\n     from hermes_cli.web_server import start_server\n \n     # The in-browser Chat tab (the embedded TUI over PTY/WebSocket) is always"]},{"file":"tests/hermes_cli/test_dashboard_unified_launch.py","hunks":["@@ -128,3 +128,45 @@ def test_reexec_child_does_not_reroute(self, main_mod, monkeypatch):\n         with pytest.raises((SystemExit, AttributeError, ImportError, TypeError)):\n             main_mod.cmd_dashboard(_args(open_profile=\"worker_x\"))\n         assert execs == []\n+\n+    def test_dashboard_starts_mcp_discovery_for_ws_backend(self, main_mod, monkeypatch):\n+        \"\"\"The dashboard process serves the /api/ws gateway but never runs\n+        tui_gateway/entry.py, so it must kick off MCP discovery itself or\n+        desktop sessions never see a profile's MCP tools.\"\"\"\n+        monkeypatch.setattr(\n+            \"hermes_cli.profiles.get_active_profile_name\", lambda: \"default\"\n+        )\n+        monkeypatch.delenv(\"HERMES_WEB_DIST\", raising=False)\n+        monkeypatch.setattr(main_mod, \"_sync_bundled_skills_quietly\", lambda: None)\n+        monkeypatch.setattr(main_mod, \"_build_web_ui\", lambda *_a, **_k: True)\n+        monkeypatch.setitem(sys.modules, \"fastapi\", types.SimpleNamespace())\n+        monkeypatch.setitem(sys.modules, \"uvicorn\", types.SimpleNamespace())\n+        monkeypatch.setitem(\n+            sys.modules,\n+            \"hermes_logging\",\n+            types.SimpleNamespace(setup_logging=lambda **_k: None),\n+        )\n+        monkeypatch.setitem(\n+            sys.modules,\n+            \"hermes_cli.plugins\",\n+            types.SimpleNamespace(discover_plugins=lambda: None),\n+        )\n+        calls = []\n+        monkeypatch.setattr(\n+            \"hermes_cli.mcp_startup.start_background_mcp_discovery\",\n+            lambda **kwargs: calls.append(kwargs),\n+        )\n+        monkeypatch.setitem(\n+            sys.modules,\n+            \"hermes_cli.web_server\",\n+            types.SimpleNamespace(start_server=lambda **_kwargs: None),\n+        )\n+\n+        main_mod.cmd_dashboard(_args())\n+\n+        assert calls == [\n+            {\n+                \"logger\": main_mod.logger,\n+                \"thread_name\": \"dashboard-mcp-discovery\",\n+            }\n+        ]"]},{"file":"tests/test_tui_gateway_server.py","hunks":["@@ -6096,6 +6096,24 @@ def test_make_agent_reads_nested_max_turns(monkeypatch):\n     assert mock_agent.call_args.kwargs[\"max_iterations\"] == 200\n \n \n+def test_make_agent_waits_for_shared_mcp_discovery(monkeypatch):\n+    _setup_make_agent_mocks(monkeypatch, {})\n+    waited = []\n+\n+    from hermes_cli import mcp_startup\n+\n+    monkeypatch.setattr(\n+        mcp_startup,\n+        \"wait_for_mcp_discovery\",\n+        lambda timeout=0.75: waited.append(timeout),\n+    )\n+\n+    with patch(\"run_agent.AIAgent\"):\n+        server._make_agent(\"sid1\", \"key1\")\n+\n+    assert waited == [0.75]\n+\n+\n def test_make_agent_nested_max_turns_takes_priority(monkeypatch):\n     _setup_make_agent_mocks(\n         monkeypatch, {\"agent\": {\"max_turns\": 500}, \"max_turns\": 100}"]},{"file":"tui_gateway/server.py","hunks":["@@ -3072,11 +3072,17 @@ def _make_agent(\n     from hermes_cli.runtime_provider import resolve_runtime_provider\n \n     # MCP tool discovery runs in a background daemon thread at startup so a\n-    # dead server can't freeze the shell (see tui_gateway/entry.py).  The agent\n-    # snapshots its tool list once here and never re-reads it, so briefly wait\n-    # for in-flight discovery to land before building — bounded, so a slow/dead\n-    # server still can't block.  No-op once discovery has finished (every build\n-    # after the first during a slow startup).\n+    # dead server can't freeze the shell.  The agent snapshots its tool list\n+    # once here and never re-reads it, so briefly wait for in-flight discovery\n+    # to land before building — bounded, so a slow/dead server still can't\n+    # block. Dashboard /api/ws uses hermes_cli.mcp_startup; TUI stdio keeps\n+    # its existing tui_gateway.entry-owned thread.\n+    try:\n+        from hermes_cli.mcp_startup import wait_for_mcp_discovery\n+\n+        wait_for_mcp_discovery()\n+    except Exception:\n+        pass\n     try:\n         from tui_gateway.entry import wait_for_mcp_discovery\n \n"]}]},{"kind":"pr","number":54161,"title":"fix(tui): start MCP discovery for websocket sessions (#38945)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["infographic/mcp-ws-discovery/infographic.png","tests/test_tui_gateway_ws.py","tui_gateway/ws.py"]},{"kind":"pr","number":61922,"title":"fix(tui): MCP discovery for slash_worker and TUI agent builds (#61891)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#61949","number":61949}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":61949,"title":"fix(tui): discover MCP tools in slash workers","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tui_gateway/test_slash_worker_mcp_discovery.py","tui_gateway/slash_worker.py"],"fix_hunks":[{"file":"tests/tui_gateway/test_slash_worker_mcp_discovery.py","hunks":["@@ -0,0 +1,105 @@\n+\"\"\"Integration coverage for profile-local MCP discovery in slash workers.\"\"\"\n+\n+from __future__ import annotations\n+\n+import json\n+import os\n+from pathlib import Path\n+import queue\n+import subprocess\n+import sys\n+import textwrap\n+import threading\n+\n+import pytest\n+import yaml\n+\n+pytest.importorskip(\"mcp.server.fastmcp\")\n+\n+\n+def test_profile_local_mcp_tool_is_visible_in_slash_worker(tmp_path):\n+    profile_home = tmp_path / \"profile-home\"\n+    profile_home.mkdir()\n+    marker = \"profile-local-61922\"\n+    server = tmp_path / \"fastmcp_probe.py\"\n+    server.write_text(\n+        textwrap.dedent(\n+            f\"\"\"\n+            from mcp.server.fastmcp import FastMCP\n+\n+            mcp = FastMCP(\"profileprobe\")\n+\n+            @mcp.tool()\n+            def hermes_61922_profile_probe() -> str:\n+                return {marker!r}\n+\n+            if __name__ == \"__main__\":\n+                mcp.run(transport=\"stdio\")\n+            \"\"\"\n+        ),\n+        encoding=\"utf-8\",\n+    )\n+    (profile_home / \"config.yaml\").write_text(\n+        yaml.safe_dump(\n+            {\n+                \"mcp_servers\": {\n+                    \"profileprobe\": {\n+                        \"enabled\": True,\n+                        \"command\": sys.executable,\n+                        \"args\": [str(server)],\n+                    }\n+                }\n+            }\n+        ),\n+        encoding=\"utf-8\",\n+    )\n+\n+    env = os.environ.copy()\n+    for key in list(env):\n+        if key.endswith(\"_API_KEY\") or key.endswith(\"_TOKEN\"):\n+            env.pop(key)\n+    env[\"HERMES_HOME\"] = str(profile_home)\n+    env[\"PYTHONPATH\"] = str(Path(__file__).resolve().parents[2])\n+    env[\"HERMES_SLASH_WATCHDOG_GRACE_S\"] = \"0\"\n+    env[\"HERMES_SLASH_WATCHDOG_POLL_S\"] = \"0.05\"\n+    proc = subprocess.Popen(\n+        [\n+            sys.executable,\n+            \"-u\",\n+            \"-m\",\n+            \"tui_gateway.slash_worker\",\n+            \"--session-key\",\n+            \"agent:main:tui:dm:mcp-profile-test\",\n+        ],\n+        stdin=subprocess.PIPE,\n+        stdout=subprocess.PIPE,\n+        stderr=subprocess.PIPE,\n+        text=True,\n+        env=env,\n+        cwd=tmp_path,\n+    )\n+    output: queue.Queue[str] = queue.Queue()\n+    try:\n+        assert proc.stdin is not None\n+        assert proc.stdout is not None\n+        stdout = proc.stdout\n+        threading.Thread(\n+            target=lambda: output.put(stdout.readline()),\n+            daemon=True,\n+        ).start()\n+        proc.stdin.write(json.dumps({\"id\": 1, \"command\": \"/tools\"}) + \"\\n\")\n+        proc.stdin.flush()\n+        try:\n+            line = output.get(timeout=10)\n+        except queue.Empty:\n+            pytest.fail(\"slash worker produced no /tools response within 10 seconds\")\n+        response = json.loads(line)\n+        assert response[\"ok\"] is True\n+        assert \"mcp__profileprobe__hermes_61922_profile_probe\" in response[\"output\"]\n+    finally:\n+        proc.terminate()\n+        try:\n+            proc.wait(timeout=5)\n+        except subprocess.TimeoutExpired:\n+            proc.kill()\n+            proc.wait(timeout=5)"]},{"file":"tui_gateway/slash_worker.py","hunks":["@@ -65,6 +65,27 @@ def _is_orphaned(original_ppid, parent_create_time, getppid=os.getppid) -> bool:\n         return True\n \n \n+def _prepare_slash_worker_runtime() -> None:\n+    \"\"\"Start bounded MCP discovery before HermesCLI snapshots tools.\n+\n+    Each slash_worker child is its own process — the parent ``hermes serve``\n+    discovery thread does not populate this registry (issue #61891).\n+    \"\"\"\n+    import logging\n+\n+    from hermes_cli.mcp_startup import (\n+        start_background_mcp_discovery,\n+        wait_for_mcp_discovery,\n+    )\n+\n+    logger = logging.getLogger(__name__)\n+    start_background_mcp_discovery(\n+        logger=logger,\n+        thread_name=\"slash-worker-mcp-discovery\",\n+    )\n+    wait_for_mcp_discovery()\n+\n+\n def _start_parent_death_watchdog(original_ppid, parent_create_time) -> None:\n     def _loop():\n         while not _is_orphaned(original_ppid, parent_create_time):","@@ -129,6 +150,7 @@ def main():\n     except psutil.Error:\n         parent_create_time = 0.0\n     _start_parent_death_watchdog(orig_ppid, parent_create_time)\n+    _prepare_slash_worker_runtime()\n \n     with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):\n         cli = HermesCLI(model=args.model or None, compact=True, resume=args.session_key, verbose=False)\n"]}]},{"kind":"pr","number":67125,"title":"fix(mcp): resolve uv and uvx under filtered PATH","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"72ed4fb0cf7d","number":0},{"kind":"pr","value":"#37665","number":37665},{"kind":"pr","value":"#67125","number":67125},{"kind":"pr","value":"#67178","number":67178},{"kind":"pr","value":"#37589","number":37589},{"kind":"pr","value":"#56537","number":56537},{"kind":"pr","value":"#56594","number":56594},{"kind":"pr","value":"#61153","number":61153},{"kind":"pr","value":"#8305","number":8305},{"kind":"pr","value":"#67873","number":67873},{"kind":"pr","value":"#56536","number":56536},{"kind":"pr","value":"#54141","number":54141},{"kind":"pr","value":"#374151","number":374151}],"category":"partial_fix","implication":"needs_llm"},"merged":false},{"kind":"pr","number":107205,"title":"fix(mcp): increase discovery timeout and sync-wait in TUI agent build","url":null,"state":"open","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":122861,"title":"fix(mcp): resolve bare uv/uvx under GUI-style PATHs (#37589)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_platform/resolver/known_dirs.py","tests/tools/test_mcp_tool_issue_948.py","tools/mcp_tool_config.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}