MCP integration
Covenant integrates with the Model Context Protocol (MCP) — the protocol agents use to discover and call external tools. The same Tool trait backs native Rust implementations and external MCP servers reached over JSON-RPC, so the daemon and any client can list and invoke tools through a single surface.
Wire shapes
Covenant's MCP types follow the public MCP shapes verbatim so a tool catalogue is portable between Covenant and any other MCP-aware host.
ToolSpec {
name: "echo",
description: "Returns the provided text argument verbatim.",
inputSchema: {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
"additionalProperties": false
}
}
ToolCallResult {
content: [ Content::Text { text: "…" }, Content::Json { value: {…} } ],
isError: false
}Native tools
Native tools are Rust types implementing the Tool trait and registered with ToolRegistry at daemon startup. The registry exposes list_specs for catalogue queries and call(name, arguments) for invocation.
Two native tools ship with the daemon out of the box:
echo— returns itstextargument verbatim. Validates the schema (rejects missing or non-stringtext); useful as a smoke test for the registry plumbing and as a reference for argument validation.clock— no-arg, returns{ "epoch_ms": <u64> }. Useful for confirming the daemon is alive without granting any capability.
Calling a tool
# Grant the per-tool capability first.
covenant capabilities grant tool.call.echo
# Then call the tool with JSON arguments.
covenant tools call echo --args '{"text":"hi"}'
# → hiEvery tools/call requires the capability tool.call.<name> in the issuer's active set. Capability checks are audited via the same CapabilityCheck event used by agent dispatch; the audit row carries an agent_id of tool:<name> so operators can distinguish tool calls from agent dispatches at a glance.
External MCP servers
External MCP servers extend the catalogue without modifying the daemon. Configure them in ~/.covenant/secrets.toml:
[[mcp.server]]
name = "filesystem"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
env = { LOG_LEVEL = "info" }On startup the daemon spawns each configured server, performs the MCP handshake (initialize → notifications/initialized → tools/list), and merges the discovered tools into the same ToolRegistry the native tools live in. Per-server failures are fail-soft: a server that fails to start or returns an error during handshake is logged and skipped without preventing the daemon from coming up.
Transport
Covenant speaks line-delimited JSON-RPC 2.0 over the spawned process's stdin/stdout. Each request carries a stable id; the daemon correlates responses by id and ignores anything unrecognised. The child process is started with kill_on_drop(true), so an unclean daemon exit also stops the child — there are no orphan tool processes.
Security
External MCP servers run as the operator. They have access to whatever the daemon's user account has access to. The capability gate (tool.call.<name>) governs who can invoke a tool through the daemon, but it cannot govern what the tool itself does on the operator's machine once invoked. Vet servers before configuring them and prefer the most narrowly-scoped server for the job.
Related
- Capability tokens — the gate on every
tools/call. - Audit log — where every capability check and every tool call lands.
- CLI —
tools listandtools call.