mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-13 09:01:54 +00:00
fix(desktop): discover MCP tools for dashboard /api/ws backends (#44512)
The desktop chat surface talks to the dashboard's in-process /api/ws gateway, which builds agents through tui_gateway.server._make_agent. That path only snapshots the existing tool registry — MCP discovery is started by tui_gateway/entry.py (the stdio TUI), which the dashboard process never runs. So a profile's configured MCP servers never connect under the desktop app and sessions show no MCP tools. Start a shared background MCP discovery thread at dashboard startup (via hermes_cli.mcp_startup, bounded so a slow/dead server can't block boot), and have _make_agent briefly join that thread in addition to the existing entry-owned TUI thread before snapshotting tools. Carved out of #44478. Co-authored-by: AJ <yspdev@gmail.com>
This commit is contained in:
parent
2ee69d0579
commit
73969771a5
4 changed files with 91 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -128,3 +128,45 @@ class TestUnifiedDashboardRouting:
|
|||
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",
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue