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 its text argument verbatim. Validates the schema (rejects missing or non-string text); 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"}'
# → hi

Every 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.
  • CLItools list and tools call.