hermes-fleet-mcp
README.md
# hermes-fleet-mcp
An MCP server that exposes a [Hermes](https://hermes-agent.nousresearch.com/docs) deployment's
state and a chat surface, so a control plane (or any MCP client) can observe and drive a remote
Hermes box. This is the **Node Bridge** — one install per box, one bearer token per box.
Read-only observability (agents, sessions, crons, skills, tools) + a `chat` tool that runs a
remote agent and returns a session handle to poll. No admin mutations.
## License & Disclaimer
**MIT licensed** — free to use, modify, and redistribute, including for commercial use.
See [LICENSE](LICENSE).
Developed by **Dynamics Mobile OOD** ([dynamicsmobile.com](https://dynamicsmobile.com)).
The software is provided **"AS IS"** for **use at your own risk**: Dynamics Mobile OOD
makes **no warranties** and accepts **no responsibility or liability** for any consequences,
damage, or loss arising from its installation or use — including anything an agent does
while driven through this bridge.
## Install (on the box)
Requires Python 3.10+ and a working `hermes` CLI on `PATH`.
```bash
pip install . # or: uv tool install .
```
## Run
```bash
# 1. generate a per-box token
hermes-fleet-mcp gen-key
# 2. serve the MCP endpoint (streamable HTTP) — bind only where you trust it
hermes-fleet-mcp serve --host 0.0.0.0 --port 8000 --token '<the key>'
# (or --token-file key.txt, or env HERMES_FLEET_MCP_TOKEN)
```
The MCP endpoint is `http://<host>:<port>/mcp`. Every request must carry
`Authorization: Bearer <token>`.
**Serve over TLS in production** — put it behind a reverse proxy (Caddy/nginx) or use an
MCP-over-TLS transport. The token is a shared secret; plain HTTP leaks it.
By default the bridge accepts any `Host` header (it's a server-to-server endpoint, so the
bearer token is the real gate). To keep DNS-rebinding protection on and whitelist your
public domain instead, add `--allowed-host yourdomain.com` (repeatable; localhost is
always allowed).
`HERMES_HOME` is honoured (default `~/.hermes`); override with `--hermes-home`.
## Docker
Run as a container — read-only observability works out of the box with `HERMES_HOME` mounted:
```bash
export HERMES_HOME=/home/hermes/.hermes
export HERMES_FLEET_MCP_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
docker compose up -d --build
```
The `chat` tool and the version field need the `hermes` CLI — see `docker-compose.yml`
for how to make it reachable (build FROM a Hermes image, or mount the host install).
## Tool surface
| Tool | Description |
|---|---|
| `node_status` | version, home, counts, configured toolsets, bridge uptime |
| `list_agents` | the node's agents (Hermes profiles) with model/provider/toolsets |
| `get_agent(name)` | one agent's detail + recent sessions |
| `list_sessions(agent?, limit?, active_only?)` | recent sessions (title, workspace, git branch, last activity) |
| `list_crons(agent?)` | configured cron jobs |
| `list_skills(agent?)` | installed skills |
| `list_tools` | union of configured toolsets |
| `chat(agent, message, caller?)` | run an agent → `{session_id, status}` |
| `get_session(session_id)` | poll a chat → `{status, reply, …}` |
`agent` maps to a Hermes profile name (`default` is the root profile). The `caller` claim on
`chat` is recorded for per-human attribution.
## How agents map to Hermes
- **Agent = Hermes profile.** `default` = `<HERMES_HOME>/config.yaml`; named agents =
`<HERMES_HOME>/profiles/<name>/config.yaml`.
- `--profile <name>` sets an isolated `HERMES_HOME`, so sessions/crons/skills are read
per-profile.
- **Chat** runs `hermes --profile <agent> -z "<message>"` (one-shot: prints only the final
reply, approvals auto-bypassed).
## Connect from an MCP client
Point any MCP client at `http://<host>:<port>/mcp` with the bearer header. Example client
config (DMOps or another MCP host):
```json
{
"mcpServers": {
"hermes-fleet": {
"url": "https://box.example.com/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
```
## Security
- **Bearer token per box** — generated by `gen-key`, validated (constant-time) on every request.
- **Read + chat only** — no admin mutation tools in this version.
- **TLS required** in production; never expose the endpoint anonymously.
- **DNS-rebinding protection** — off by default (server-to-server); opt in with
`--allowed-host <domain>` to whitelist your public host while keeping it enabled.
## Development
```bash
pip install -e ".[dev]"
pytest
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues