Files

4.6 KiB

FastMCP + Hermes MCP Server Deployment Pattern

Super Search (Jul 2026) reference implementation

Architecture

Hermes supports two MCP server transport modes for custom tools:

Mode Config Use Case
Command command: python3 + args: [...] Subprocess managed by Hermes
HTTP/URL url: http://127.0.0.1:PORT/mcp Standalone service (systemd/Docker)

For production services with auto-restart, use HTTP mode behind a systemd service.

Build Pattern

1. FastMCP Server

from fastmcp import FastMCP

mcp = FastMCP("Service Name", version="1.0.0")

@mcp.tool(description="Tool description shown to Hermes.")
async def my_tool(query: str, limit: int = 5) -> str:
    """Tool docstring."""
    # implementation
    pass

if __name__ == "__main__":
    mcp.run(transport="http", host="127.0.0.1", port=8899, path="/mcp")

Key points:

  • transport="http" exposes SSE + JSON-RPC on the HTTP path
  • Bind to 127.0.0.1 (loopback) — never expose MCP endpoints publicly
  • Each @mcp.tool becomes a discoverable tool in Hermes

2. Virtual Environment

python3 -m venv /path/to/service/venv
/path/to/service/venv/bin/pip install fastmcp httpx trafilatura  # etc.

3. Environment Variables

Secrets via .env file with systemd EnvironmentFile directive:

MY_API_KEY=sk-...

Load in Python with python-dotenv:

from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path.home() / ".hermes" / ".env")
# or use a local .env:
# load_dotenv(Path(__file__).parent / ".env")

4. Systemd Service

[Unit]
Description=My MCP Server
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/path/to/service
Environment="PATH=/path/to/service/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment="PYTHONUNBUFFERED=1"
EnvironmentFile=/path/to/.env
ExecStart=/path/to/service/venv/bin/python3 /path/to/service/server.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Install:

cp service.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now my-service

5. Hermes MCP Config Wiring

Add to ~/.hermes/config.yaml under mcp_servers::

mcp_servers:
  my-service:
    url: http://127.0.0.1:8899/mcp
    enabled: true

Or via CLI:

hermes config set mcp_servers.my-service.url 'http://127.0.0.1:8899/mcp'
hermes config set mcp_servers.my-service.enabled 'true'

6. Verification

systemctl is-active my-service
hermes mcp test my-service

Expected output:

Testing 'my-service'...
  Transport: HTTP → http://127.0.0.1:8899/mcp
  Auth: none
  ✓ Connected (XXms)
  ✓ Tools discovered: N

7. Gateway Restart (if tools don't appear in session)

# From outside the gateway session (SSH):
systemctl restart hermes.service

# Avoid using 'hermes gateway restart' from inside the Telegram session
# — it kills the process you're talking through.

Multi-Provider Fallback Pattern

For services that combine multiple backends (search, AI, extraction), use an ordered fallback chain:

async def web_search(query, limit=5):
    # Tier 1 — Free/self-hosted
    try:
        results = await free_backend(query, limit)
        if results:
            return results
    except Exception:
        pass

    # Tier 2 — Premium API
    try:
        results = await premium_backend(query, limit)
        if results:
            return results
    except Exception:
        pass

    # Tier 3 — Paid fallback (last resort)
    try:
        return await paid_backend(query, limit)
    except Exception as e:
        return {"error": str(e)}

Each tier catches exceptions independently so one backend's failure doesn't cascade.

Pitfalls

  • Systemd EnvironmentFile loads from .env format — lines must be KEY=VALUE. No export, no quotes around values (unlike shell). The file is read by systemd, not sourced by bash.
  • FastMCP HTTP transport requires Accept: application/json, text/event-stream header — simple curl requests without this header get a 406 error. Hermes' MCP client handles this automatically.
  • Port conflicts after subagent test runs — a subagent may leave a test process running on the target port. Before enabling the systemd service, check: ss -tlnp | grep :PORT. Kill stale processes with fuser -k PORT/tcp.
  • Subagent timeout for multi-step builds — the default 600s child timeout may be insufficient for pip installs + service wiring. Either raise delegation.child_timeout_seconds or plan to handle the final 20% (install + enable + test) yourself.