External MCP bridge
colibri-mcp is the Model Context Protocol bridge between Colibri and
MCP-capable editors (Zed, Cursor, Windsurf, Claude Code). It exposes the
current daemon state as MCP tools today and acts as a small MCP host for
arbitrary external stdio MCP servers as a prototype.
Why MCP?
Section titled “Why MCP?”The daemon already exposes a typed Unix-socket API through
crates/colibri-client. MCP wraps that API into the standard JSON-RPC tool
protocol that editors already speak. This avoids the maintenance cost and
political risk of forking or embedding an editor, keeps Colibri headless-safe,
and lets any MCP-compatible client access the same surface.
For the longer-term product framing, see ../CLAWDIE-STUDIO-PROPOSAL.md.
Two roles in one binary
Section titled “Two roles in one binary”colibri-mcp serves as both:
- MCP server for Colibri — presents tools such as
colibri_status,colibri_snapshot,colibri_list_tasks,colibri_create_task, etc. - MCP host for external servers — reads a registry file, spawns configured
proc ess servers, and proxies
tools/listandtools/callto them.
Separating these roles would create a second binary for little gain; hosting external servers is gated so the default surface stays read-only.
Daemon socket resolution
Section titled “Daemon socket resolution”The MCP server must reach the daemon. The socket path is resolved in order:
--socketCLI flagCOLIBRI_MCP_SOCKETCOLIBRI_DAEMON_SOCKETDaemonConfig::from_env().socket_path(env-driven defaults)
This mirrors how the operator CLI and TUI resolve the same socket.
Colibri tools and gates
Section titled “Colibri tools and gates”| Tool | Default | Gate |
|---|---|---|
colibri_status | read-only | none |
colibri_snapshot | read-only | none |
colibri_list_tasks | read-only | none |
colibri_list_skills | read-only | none |
colibri_create_task | write-gated | COLIBRI_MCP_WRITE=1 / --write |
colibri_intake_task | write-gated | COLIBRI_MCP_WRITE=1 / --write |
colibri_set_cost_mode | write-gated | COLIBRI_MCP_WRITE=1 / --write |
The default ISO posture is read-only. Mutating commands require the operator to opt in explicitly, which prevents an assistant from creating tasks or switching cost mode by accident.
External MCP host
Section titled “External MCP host”The prototype external-host tools are always exposed but only allow calling an
external tool when the separate COLIBRI_MCP_EXTERNAL_CALL=1 / --external-call
flag is set.
Registry
Section titled “Registry”External servers are configured from a JSON registry. Default path:
/usr/local/etc/colibri/external-mcp.json. Override with
COLIBRI_MCP_EXTERNAL_CONFIG or --external-config.
Each entry declares a command, args, optional env, and optional jail confinement:
{ "servers": { "demo": { "command": "/usr/local/bin/demo-mcp-server", "args": ["--stdio"], "env": { "DEMO_MODE": "1" }, "jail": { "name": "mcp0", "root_path": "/usr/local/bastille/jails/mcp0/root" } } }}Confinement
Section titled “Confinement”External MCP servers execute arbitrary code on the operator machine, so they
reuse the same jail primitive as agent spawning:
colibri_daemon::spawner::{prepare_spawn_command, jail_wrap, JailConfig, PrivMode}.
jail.nameenters an existing persistent jail viajexec.jail.root_pathcreates an ephemeral jail for the duration of the call.- Omitting
jailruns the server on the host, but stdin/stdout framing is the same either way.
The root-only jail step honors the shared COLIBRI_JAIL_PRIV_MODE policy (mdo
on the operator USB, helper on deployed hosts). See jail-confinement.
Request lifecycle
Section titled “Request lifecycle”Every external tools/list or tools/call request:
- Spawns a fresh process (
ExternalMcpSession::start) using the shared spawner. - Runs the MCP
initializehandshake with protocol version2024-11-05. - Sends
tools/listortools/call, reads the response over newline-delimited JSON, and returns the result. - Kills the child and removes the staged cleanup directory.
This is intentionally simple: one process per request, no connection pool, no streaming, no long-lived state. It is good enough for prototyping; a production host should add policy, audit logging, secret management, and per-tool permissions.
Why separate COLIBRI_MCP_WRITE and COLIBRI_MCP_EXTERNAL_CALL
Section titled “Why separate COLIBRI_MCP_WRITE and COLIBRI_MCP_EXTERNAL_CALL”COLIBRI_MCP_WRITE gates mutations against the local Colibri daemon. External
tool calls execute arbitrary third-party binaries and therefore live on a
different trust surface. Requiring two separate opt-ins makes accidental
privilege escalation harder.
Limits and open questions
Section titled “Limits and open questions”- stdio transport only
- one external process per request
- no server/tool allowlist beyond the registry file
- no streaming tool results
- no production secret manager integration
Those limits are recorded as explicitly accepted for now; if the prototype is promoted to default ISO behavior, each limit should be addressed.
See also
Section titled “See also”- jail-confinement — jail policy reused for external MCP servers
- cost-model — cost mode and the write-gated
colibri_set_cost_mode - skills-catalog — read-only skill catalog exposed via
colibri_list_skills