clermes-mcp
README.md
# clermes-mcp
An MCP server that exposes a Clermes worker fleet as typed tools over HTTP and stdio.
Clermes is a file-based coordination harness for a fleet of long-lived coding-agent workers. The bash dispatcher at [`canopy-workshop/clermes`](https://github.com/canopy-workshop/clermes) is the source of truth for tmux and session state. `clermes-mcp` is a thin Python shell over that dispatcher, so a coordinator that speaks MCP (Hermes, Claude, an editor MCP client, anything) can drive the fleet with structured tool calls instead of scraping panes and writing poll loops.
`clermes-mcp` is an enhancement, not a requirement. The `clermes` CLI is fully usable on its own. This server is the integration layer that makes a Clermes fleet a first-class citizen in multiagent workflows.
The reference worker is Claude Code. The reference coordinator is Hermes. Any other coding agent (Codex, Aider, antigravity, opencode) and any other MCP-speaking coordinator fit the same harness. Nothing about the protocol or this server is agent-specific.
## What you get
- **Typed read tools** for fleet inventory, pane capture, lock and dispute inspection.
- **Typed action tools** (operator role only) for dispatch, respawn, reply, halt, spawn, kill, fleet-wide heal.
- **Blocking or async per call.** The client picks. Blocking returns the done file inline. Async returns a job id and the caller polls.
- **Auto-heal before send.** If a worker pane is in bash, dead, or some other state, dispatch kills and respawns it before sending.
- **Anti-compact resend.** After dispatch, the server watches for the conversation-compacted-then-idle pane state and re-sends the brief once if it sees the swallow signature.
- **Role gating.** `operator` exposes the full surface. `worker` exposes read tools only. A worker-role MCP client cannot see dispatch or kill at all. The gate is structural: the action tools are never registered in that role.
- **No outbound to humans.** The server never exposes a tool that contacts humans. I treat that as a fixed invariant of the project. Don't fork it back in.
## Install
```
pip install clermes-mcp
```
You also need:
- The `clermes` bash dispatcher on your PATH (or at `$CLERMES_HOME/clermes`).
- `tmux` and `jq` on the host running the worker fleet.
- A worker fleet (Claude Code, or any equivalent CLI agent with an interactive TUI).
## Configure
Everything resolves from environment variables. Defaults work for a typical install at `~/.clermes`.
| Setting | Env var | Default |
| ---------------- | ------------------ | -------------------------------------------------------------- |
| Clermes home | `CLERMES_HOME` | `~/.clermes` |
| Dispatcher bin | `CLERMES_BIN` | `$CLERMES_HOME/clermes`, then `$(which clermes)` |
| Bind host | `CLERMES_MCP_HOST` | `127.0.0.1` |
| Bind port | `CLERMES_MCP_PORT` | `9675` |
| Bearer token | `CLERMES_MCP_TOKEN`| unset (server warns on HTTP transport) |
| Role | `CLERMES_MCP_ROLE` | `operator` |
| Default mode | `CLERMES_MCP_MODE` | `blocking` |
If `$CLERMES_HOME/voice-preamble.txt` exists, its contents prepend to dispatches with `public_artifact=true`. Otherwise a neutral default applies. The preamble is the right place to put your repo's voice rules. Don't bake them into the server.
`config.yaml` under `$CLERMES_HOME` is read if present, for the `paths.home` override only. Every other field is informational.
Run the diagnostic check:
```
clermes-mcp doctor
```
It verifies paths, the dispatcher, the registry, and protocol directories.
## Run
HTTP (recommended for multiagent integration):
```
clermes-mcp serve --transport http --host 127.0.0.1 --port 9675
```
stdio (for local subprocess clients):
```
clermes-mcp serve --transport stdio
```
To inspect the schema for every tool:
```
clermes-mcp tools
```
## Run as a service
Drop the unit file at `examples/clermes-mcp.service` into `~/.config/systemd/user/` and enable it:
```
systemctl --user daemon-reload
systemctl --user enable --now clermes-mcp
journalctl --user -u clermes-mcp -f
```
On systems where user-systemd lingering is unreliable (WSL across `wsl --shutdown`, some headless setups), use whatever your platform's session-supervisor of choice is. A simple cron `@reboot` line works too. tmux is a last resort because the MCP server has no TTY needs.
## Wire into a coordinator
The reference is Hermes:
```yaml
mcp_servers:
clermes:
url: "http://127.0.0.1:9675/mcp"
headers:
Authorization: "Bearer ${CLERMES_MCP_TOKEN}"
timeout: 1800
connect_timeout: 30
```
A 30-minute timeout lines up with the blocking ceiling on long dispatches. For async use, the request returns immediately with a job id and the timeout can drop to anything.
Other MCP-speaking coordinators wire in the same way.
## Tool surface
**Read tools (every role):**
- `list_workers()`: flat list of `{name, state, alive, locked, task_id?, workdir}`.
- `fleet_snapshot()`: full per-worker pane text and classification.
- `capture(worker, lines=80)`: pane tail with classification.
- `worker_health(worker)`: `claude | bash | dead | other:<proc>`.
- `list_open_tasks()`: lock files joined with the assigned-task map.
- `read_dispute(task_id)`: raw markdown of a dispute file, or null.
- `read_result(task_id)`: parsed JSON of a done file, or null.
- `list_done(limit=50)`: recent completed task ids.
- `list_jobs() / job_status(job_id) / job_result(job_id)`: async job plumbing.
**Action tools (operator role only):**
- `dispatch(worker, brief, slug?, wait=true, mode=blocking, timeout=1800, public_artifact=false, auto_heal=true, anti_compact_resend=true)`: the headline tool.
- `respawn(worker)`: kill then spawn (the dispatcher's `heal` is fleet-wide; this is the single-worker equivalent).
- `spawn(worker) / kill(worker) / heal_fleet()`: direct dispatcher wrappers.
- `reply(worker, message, wait_for_task_id?, mode=blocking, timeout=1800)`: send without a new `TASK_ID` footer. Use to answer a dispute or select an injection-dialog option.
- `halt(task_id)`: touch the halt file so an announced action does not proceed past its grace window.
## Security model
- **Bind localhost by default.** `127.0.0.1:9675`. Don't bind `0.0.0.0` without a reverse proxy and a token in front of it.
- **Bearer token auth on HTTP.** Set `CLERMES_MCP_TOKEN` and require it on every HTTP request. Without it the server logs a warning at startup and accepts anything.
- **Role gating.** `CLERMES_MCP_ROLE=worker` strips every action tool from the registered surface. A worker-role MCP client cannot see, let alone call, dispatch or kill.
- **No outbound to humans.** Nothing in the tool surface contacts email, SMS, agencies, webhooks, or chat. Adding one breaks the threat model of this project; don't do it.
- **No `shell=True` anywhere.** All subprocess calls pass argv as a list. Briefs are arbitrary text and cannot escape the process boundary.
- **Token redaction** in error strings surfaced to the client.
## Layout
```
clermes-mcp/
├── README.md
├── LICENSE
├── pyproject.toml
├── .env.example
├── src/clermes_mcp/
│ ├── __init__.py
│ ├── __main__.py CLI entry point
│ ├── server.py FastMCP app, tool registration, transport wiring
│ ├── dispatcher.py async subprocess wrapper around the bash CLI
│ ├── protocol.py file I/O for done/dispute/announce/halt/locks/assigned
│ ├── pane.py pane-text classification heuristics
│ ├── jobs.py in-process async job registry
│ ├── config.py env + optional config.yaml loader
│ ├── doctor.py diagnose paths, dispatcher, deps
│ └── models.py pydantic schemas
├── tests/ pytest suite, all hermetic against the stub
├── examples/
│ ├── stub-clermes fake dispatcher for CI and demo
│ ├── clermes-mcp.service systemd --user unit
│ └── hermes-config.snippet.yaml
└── docs/PROTOCOL.md generic coordination contract
```
## Testing
```
pip install -e ".[dev]"
pytest
ruff check src tests
```
Every test runs against `examples/stub-clermes`. No live fleet, no tmux. CI on its own can validate the full server surface.
## Status
Pre-1.0. The tool surface is stable enough to ship, but expect minor breakage in tool names and arg shapes until the integration with at least two coordinators settles.
## License
MIT.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues