New docs/MCP-INTEGRATION.md: how the two Hermes instances connect via MCP using colibri-mcp as the hub-and-spoke front-end to the shared board, rather than a direct mesh. Grounded in actual code: - Hermes is both MCP server (hermes mcp serve) and client (mcp_servers config) - colibri-mcp tool surface + env vars (COLIBRI_MCP_SOCKET/WRITE), socket transport - ties into the live board + poller/worker loop and the socat cross-host bridge - LIVE/SETUP/PLANNED tags; security, rejected mesh alternative, external-MCP future Cross-linked from CAPABILITY-ROUTING.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.2 KiB
MCP Integration — Colibri as the Agent Coordination Hub
LIVE VS PLANNED. The building blocks are all real and in the repos today
(Hermes speaks MCP both directions; colibri-mcp exists; the board + poller/worker
loop and the cross-host bridge are live). What is not yet wired is the one setup
step this document describes: pointing each Hermes at colibri-mcp so the two
instances coordinate through the shared board. Sections are tagged [LIVE],
[SETUP] (the work to do), or [PLANNED].
1. What MCP is — and what "connect two Hermes" actually means
MCP (Model Context Protocol) is a client → server tool-calling protocol over JSON-RPC. A client (an agent's LLM loop) connects to a server that advertises tools and resources; the client calls them and gets results. It is not a peer-to-peer chat bus and not a message queue.
So "connect two Hermes instances" has two distinct meanings:
- (a) Tool sharing — Hermes A invokes Hermes B's own tools (B's browser, B's files) by treating B as an MCP server. Point-to-point.
- (b) Coordination — the two instances hand work back and forth and share state.
Our fleet already does (b) through the Colibri board (register-agent → poll →
execute → done; see CAPABILITY-ROUTING.md). The simplest,
highest-leverage MCP move is therefore to make MCP the in-conversation interface to
that board, not to wire the two Hermes mouth-to-mouth.
Expectation-set: this gives each Hermes's LLM conversational read/write access to the shared board. It is not a live two-way chat between the instances. The board is the shared state they meet at — durable, inspectable, restart-safe.
2. [LIVE] What already exists
Hermes is both an MCP server and an MCP client.
- Server —
hermes mcp serve(mcp_serve.py, FastMCP over stdio) exposes Hermes tools/conversations to any MCP client. Client config shape:{ "mcpServers": { "hermes": { "command": "hermes", "args": ["mcp", "serve"] } } } - Client — Hermes consumes external MCP servers from its
mcp_serversconfig (hermes_cli/mcp_config.py, managed via thehermes mcpsubcommand; loaded bytui_gateway/server.py; refreshable in-session withreload-mcp). Each entry is the standardcommand/args/env(orurl) shape; presets exist (e.g. Codex).
Colibri ships a ready-made MCP server fronting the board: colibri-mcp.
-
Crate
crates/colibri-mcp(binarycolibri-mcp), a stdio JSON-RPC MCP server that wrapscolibri-clientand talks to the daemon over its Unix socket. -
Tool surface (
crates/colibri-mcp/src/lib.rs):Tool Access Description colibri_statusread Daemon status (agents, sessions) colibri_snapshotread Glasspane snapshot (pane states) colibri_list_tasksread Tasks by status colibri_list_skillsread Registered skills catalog colibri_create_taskwrite-gated Create a task colibri_intake_taskwrite-gated Submit intake task with required_capabilitiescolibri_set_cost_modewrite-gated Switch cost mode (fast/smart/max) -
Environment (
crates/colibri-mcp/src/main.rs):COLIBRI_MCP_SOCKET— daemon socket path (override)COLIBRI_DAEMON_SOCKET— fallback socket pathCOLIBRI_MCP_WRITE=1— enable the write-gated toolsCOLIBRI_MCP_EXTERNAL_CONFIG/COLIBRI_MCP_EXTERNAL_CALL=1— proxy external MCP servers (see §6)
-
Default daemon socket on FreeBSD:
/var/run/colibri/colibri.sock(from thecolibri_daemonrc.d).colibri-mcp socket-pathprints the resolved path.
Cross-host reach is already solved — colibri-mcp connects to a daemon socket; a
remote daemon is reached via the socat bridge on 100.72.229.63:9190 (Tailscale-only;
see CAPABILITY-ROUTING [LIVE] Cross-host topology). osa-local instances just use the
local socket.
3. Architecture — hub-and-spoke, not mesh
Hermes-osa-cli ──MCP──┐ ┌──MCP── Hermes-osa-web
▼ ▼
colibri-mcp (stdio JSON-RPC, one per Hermes)
│ │
└────► colibri-daemon / board (SQLite) ◄────┘
▲ poller (2 min) / worker (5 min) loop
│ executes tasks assigned by agent UUID
Each Hermes configures Colibri once. Adding a third agent is one more spoke — no N×N wiring. The instances never connect to each other directly; they meet at the board.
Flow: Hermes A's LLM calls colibri_create_task {required_capabilities:["freebsd"]}
→ the daemon's scheduler assigns it to a matching agent's UUID → that agent's poll loop
(scripts/colibri_poll.py) picks up its own tasks, executes, and marks done
(scripts/colibri_task_done.py) → A reads the result with colibri_list_tasks.
This layers cleanly on the coordination model already built:
| Layer | Role | Source of truth |
|---|---|---|
colibri-mcp tools |
conversational read/write to the board (this doc) | — |
| poller / worker loop | autonomous execution of assigned tasks | scripts (PR #83) |
| board (SQLite) | shared state: agents, tasks, lifecycle | colibri-store |
4. [SETUP] Wiring it up (config, not code)
Per Hermes instance on osa:
- Provide the binary. Build or stage
colibri-mcp:
Confirm it reaches the daemon:cargo build --release -p colibri-mcp # target/release/colibri-mcpcolibri-mcp socket-path. - Register the server in each Hermes's
mcp_serversconfig (viahermes mcp addor the config file), giving the two instances distinct agent identities:mcp_servers: colibri: command: /usr/local/bin/colibri-mcp env: COLIBRI_MCP_SOCKET: /var/run/colibri/colibri.sock COLIBRI_MCP_WRITE: "1" # enable create/intake - Reload tools —
reload-mcpin each Hermes; confirm thecolibri_*tools appear. - Validate end-to-end — from cli-Hermes, create a
freebsdtask; confirm web-Hermes's loop runs it and the task flips todone.
Keep the two instances on separate
HERMES_HOME(shared.envis fine, shared state home is not — single-writer rule). Give them distinguishing capability tags if a task must land on a specific one (e.g.web-uivscli).
5. [LIVE] Security
- Write tools are gated by
COLIBRI_MCP_WRITE=1. Leave it unset for read-only agents; set it only where an instance should create/assign work. - Socket, not network.
colibri-mcptalks to the daemon's Unix socket; the only network surface is the bridge, bound to the Tailscale IP with apfrule — never0.0.0.0. - License:
colibri-mcpis AGPL-3.0-only; keep that in mind for any redistribution.
6. [PLANNED] Beyond coordination
- External MCP proxying.
colibri-mcpcan host third-party MCP servers (COLIBRI_MCP_EXTERNAL_CONFIG+COLIBRI_MCP_EXTERNAL_CALL=1), jail-wrapped on FreeBSD (colibri-mcpexternal.rs→colibri_daemon::spawner::jail_wrap). This lets the hub aggregate outside tools behind one MCP endpoint, confined per the capability/isolation model. - Tool-sharing mode (Option A). If a real need arises for one Hermes to call
another's own tools, expose the target with
hermes mcp serveand add it as a spoke — but prefer the board for coordination; reserve direct tool-sharing for genuine capability borrowing, and accept the point-to-point cost.
7. Rejected alternative: direct Hermes ↔ Hermes mesh
Connecting A's client straight to B's hermes mcp serve was considered and not
chosen for coordination: it is a mesh (N×N config), stdio transport would have A
spawn a new B rather than reach the running one, and it bypasses the board that
already gives us durable, inspectable shared state. The hub (Option B above) reuses
everything and scales by adding spokes.
See CAPABILITY-ROUTING.md for the routing engine and
cross-host transport, and ../AGENTS.md for the agent matrix.