4.6 KiB
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.toolbecomes 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
EnvironmentFileloads from.envformat — lines must beKEY=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-streamheader — simplecurlrequests 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 withfuser -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.