Environment
- Hermes Agent v0.10.0 (2026.4.16)
- macOS 26.3.1 (arm64)
- Python 3.11.14
- Node v25.9.0
- Gateway managed by launchd (
ai.hermes.gateway.plist)
Summary
A stdio-based MCP server that works with hermes mcp add / hermes mcp test and with hermes chat -q … (CLI) never becomes reachable from the Telegram-facing gateway. The gateway silently fails to connect — no successful MCP: registered N tool(s) log line, no MCP child processes of the gateway PID — and sessions are built with only the built-in toolsets.
Reproduction
- Register a local stdio MCP server (Node subprocess):
hermes mcp add myserver --command node --args /path/to/server.mjs --env DATABASE_URL=...
hermes mcp test myserver → ✓ connected in ~200 ms, 26 tools discovered.
hermes chat -Q -q \"call tool X\" (CLI) → works.
- Restart the gateway (
hermes gateway restart), open a Telegram session, /new, then ask anything that should use the MCP.
- Observed:
pgrep -P <gateway-pid> → no child node process.
/reload-mcp on Telegram → No MCP servers connected.
gateway.error.log:
WARNING tools.mcp_tool: Failed to connect to MCP server 'myserver' (command=node): CancelledError
- Per-session tool list dumped from
~/.hermes/sessions/*.json contains only built-in toolsets (e.g. 29 entries, none prefixed mcp_).
hermes tools list --platform telegram still reports myserver all tools enabled — misleading since the server was never actually registered in the gateway process.
What works vs. what doesn't
| Context |
Result |
hermes mcp test |
✓ connects (~200 ms) |
hermes chat -q … (CLI one-shot) |
✓ MCP tools callable |
| Gateway (launchd-managed, Telegram) — stdio transport |
✗ never registers, CancelledError |
| Gateway (launchd-managed, Telegram) — HTTP transport |
✓ works reliably |
Workaround
Switching the same server to HTTP transport (StreamableHTTPServerTransport bound to 127.0.0.1:PORT) makes it register immediately and work end-to-end in Telegram sessions. So the bug appears specific to stdio + gateway-under-launchd.
Hypothesis
discover_mcp_tools() is invoked via nested executors (loop.run_in_executor(None, discover_mcp_tools) → _run_on_mcp_loop on a background thread) and the stdio client's subprocess is spawned via anyio. The outer call appears to be cancelled before the stdio handshake completes, producing the silent CancelledError. Worth checking why the cancellation happens in the gateway context but not in a plain Python REPL or hermes chat.
Evidence attached
Happy to provide full gateway.log / gateway.error.log snippets and a minimal repro repo if helpful.
Environment
ai.hermes.gateway.plist)Summary
A stdio-based MCP server that works with
hermes mcp add/hermes mcp testand withhermes chat -q …(CLI) never becomes reachable from the Telegram-facing gateway. The gateway silently fails to connect — no successfulMCP: registered N tool(s)log line, no MCP child processes of the gateway PID — and sessions are built with only the built-in toolsets.Reproduction
hermes mcp test myserver→ ✓ connected in ~200 ms, 26 tools discovered.hermes chat -Q -q \"call tool X\"(CLI) → works.hermes gateway restart), open a Telegram session,/new, then ask anything that should use the MCP.pgrep -P <gateway-pid>→ no childnodeprocess./reload-mcpon Telegram →No MCP servers connected.gateway.error.log:~/.hermes/sessions/*.jsoncontains only built-in toolsets (e.g. 29 entries, none prefixedmcp_).hermes tools list --platform telegramstill reportsmyserver all tools enabled— misleading since the server was never actually registered in the gateway process.What works vs. what doesn't
hermes mcp testhermes chat -q …(CLI one-shot)CancelledErrorWorkaround
Switching the same server to HTTP transport (
StreamableHTTPServerTransportbound to127.0.0.1:PORT) makes it register immediately and work end-to-end in Telegram sessions. So the bug appears specific to stdio + gateway-under-launchd.Hypothesis
discover_mcp_tools()is invoked via nested executors (loop.run_in_executor(None, discover_mcp_tools)→_run_on_mcp_loopon a background thread) and the stdio client's subprocess is spawned viaanyio. The outer call appears to be cancelled before the stdio handshake completes, producing the silentCancelledError. Worth checking why the cancellation happens in the gateway context but not in a plain Python REPL orhermes chat.Evidence attached
Happy to provide full
gateway.log/gateway.error.logsnippets and a minimal repro repo if helpful.