Summary
MCP servers configured in config.yaml with enabled: true are fully functional in hermes chat CLI sessions (65 tools registered, callable) but are completely absent from the toolset in desktop app / TUI sessions served by hermes serve. The agent has no MCP tools available and cannot call them.
Impact: This effectively makes MCP servers unusable from the desktop app, which is the primary interface for non-CLI users. The server is configured, healthy, and reports as enabled — but the agent simply has no access to its tools.
Environment
- Hermes Agent: 0.18.2 (build 2026.7.7.2, upstream f8361d2)
- Platform: WSL2 (Ubuntu), desktop Electron app connecting to
hermes serve backend
- MCP server:
zotero-mcp-server 0.6.1 (stdio transport, FastMCP 3.4.4)
- Session type where it works:
hermes chat (CLI, one-shot)
- Session type where it fails: Desktop app →
hermes serve → tui_gateway.slash_worker
Reproduction
- Configure an MCP server in
config.yaml (any stdio server — Zotero in my case)
- Verify it works:
hermes chat "use zotero_search_items to find X" — MCP tools load, call succeeds
- Open the desktop app (or any TUI session via
hermes serve)
- Ask the agent to use the same MCP tool — it doesn't appear in the toolset; the agent has no way to call it
hermes mcp list reports ✓ enabled in both cases — the discrepancy is between config state and runtime tool loading.
Evidence from agent.log
CLI sessions — MCP discovery runs before every turn, 65 tools register:
00:04:56 INFO tools.mcp_tool: MCP server 'zotero' (stdio): registered 65 tool(s)
00:04:56 INFO tools.mcp_tool: MCP: registered 65 tool(s) from 1 server(s)
00:04:58 INFO agent.turn_context: conversation turn: session=... platform=cli history=0
TUI sessions — no MCP registration at all, turn proceeds with zero MCP tools:
00:19:53 INFO agent.turn_context: conversation turn: session=... platform=tui history=328
Every platform=cli turn in the log is preceded by an MCP registration line. No platform=tui turn has one.
Proposed root cause (analysis, not confirmed)
This appears to be a process-boundary gap in how hermes serve delegates to tui_gateway.slash_worker.
The parent hermes serve process runs MCP discovery at startup via tui_gateway/entry.py (lines 325–347), which spawns a tui-mcp-discovery background thread calling discover_mcp_tools(). This populates the parent's in-memory tool registry.
Each conversation session, however, runs in a child subprocess (python3 -m tui_gateway.slash_worker --session-key ... --model ...). The slash_worker creates its own HermesCLI instance, which eventually calls get_tool_definitions(). That wrapper (cli.py ~line 844) calls wait_for_mcp_discovery() — but in the subprocess, _mcp_discovery_thread is None (nobody called start_background_mcp_discovery() in this process), so the wait returns immediately. model_tools.get_tool_definitions() then builds the tool list from a registry that has no MCP tools, because discover_mcp_tools() was never called in the slash_worker's process.
Standalone CLI sessions work because each hermes chat invocation runs start_background_mcp_discovery() on its own startup path, within its own process.
The relevant code path in slash_worker.py (lines 133–134):
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)
No MCP discovery is triggered here or anywhere else in the slash_worker lifecycle.
Process layout (observed)
| PID |
Command |
Parent |
Role |
| 3051 |
hermes serve --host 0.0.0.0 --port 9119 |
— |
TUI gateway (has MCP tools in its registry) |
| 19116 |
tui_gateway.slash_worker --session-key ... |
3051 |
Session subprocess (no MCP tools) |
Notes
- The MCP server itself is healthy —
mcp-stderr.log shows clean startup/shutdown cycles with no errors on recent entries.
- Credentials are intact in
config.yaml (not redacted by the secret sanitizer).
hermes mcp list reads config, not the runtime tool registry, so it reports ✓ enabled regardless.
- This likely affects all MCP servers, not just Zotero — any stdio/SSE/HTTP MCP server configured in
config.yaml would be absent from TUI sessions.
Summary
MCP servers configured in
config.yamlwithenabled: trueare fully functional inhermes chatCLI sessions (65 tools registered, callable) but are completely absent from the toolset in desktop app / TUI sessions served byhermes serve. The agent has no MCP tools available and cannot call them.Impact: This effectively makes MCP servers unusable from the desktop app, which is the primary interface for non-CLI users. The server is configured, healthy, and reports as enabled — but the agent simply has no access to its tools.
Environment
hermes servebackendzotero-mcp-server0.6.1 (stdio transport, FastMCP 3.4.4)hermes chat(CLI, one-shot)hermes serve→tui_gateway.slash_workerReproduction
config.yaml(any stdio server — Zotero in my case)hermes chat "use zotero_search_items to find X"— MCP tools load, call succeedshermes serve)hermes mcp listreports✓ enabledin both cases — the discrepancy is between config state and runtime tool loading.Evidence from
agent.logCLI sessions — MCP discovery runs before every turn, 65 tools register:
TUI sessions — no MCP registration at all, turn proceeds with zero MCP tools:
Every
platform=cliturn in the log is preceded by an MCP registration line. Noplatform=tuiturn has one.Proposed root cause (analysis, not confirmed)
This appears to be a process-boundary gap in how
hermes servedelegates totui_gateway.slash_worker.The parent
hermes serveprocess runs MCP discovery at startup viatui_gateway/entry.py(lines 325–347), which spawns atui-mcp-discoverybackground thread callingdiscover_mcp_tools(). This populates the parent's in-memory tool registry.Each conversation session, however, runs in a child subprocess (
python3 -m tui_gateway.slash_worker --session-key ... --model ...). The slash_worker creates its ownHermesCLIinstance, which eventually callsget_tool_definitions(). That wrapper (cli.py~line 844) callswait_for_mcp_discovery()— but in the subprocess,_mcp_discovery_threadisNone(nobody calledstart_background_mcp_discovery()in this process), so the wait returns immediately.model_tools.get_tool_definitions()then builds the tool list from a registry that has no MCP tools, becausediscover_mcp_tools()was never called in the slash_worker's process.Standalone CLI sessions work because each
hermes chatinvocation runsstart_background_mcp_discovery()on its own startup path, within its own process.The relevant code path in
slash_worker.py(lines 133–134):No MCP discovery is triggered here or anywhere else in the slash_worker lifecycle.
Process layout (observed)
hermes serve --host 0.0.0.0 --port 9119tui_gateway.slash_worker --session-key ...Notes
mcp-stderr.logshows clean startup/shutdown cycles with no errors on recent entries.config.yaml(not redacted by the secret sanitizer).hermes mcp listreads config, not the runtime tool registry, so it reports✓ enabledregardless.config.yamlwould be absent from TUI sessions.