::docs :: reference

mcp servers

Connect any Model Context Protocol server from config.yaml — its tools face the same gate as everything else and never self-declare their risk.

Plug in any Model Context Protocol server — stdio, HTTP, or SSE — by declaring it in config.yaml. Its tools register as mcp_<server>_<tool> and join the agent behind the same approval gate as the built-ins. /mcp shows connection status and the tools each server added; /mcp reload applies a config edit without restarting.

config.yaml
mcp:
  connect_timeout: 20
  call_timeout: 60
  servers:
    github:
      command: npx
      args: ["-y", "@modelcontextprotocol/server-github"]
      env:
        GITHUB_PERSONAL_ACCESS_TOKEN: ${GITHUB_TOKEN}
    internal-docs:
      url: https://mcp.example.com/mcp
      headers:
        Authorization: Bearer ${EXAMPLE_TOKEN}
      risk: read_only

transport

stdio | http | sse — optional; inferred from command (stdio) or url (http)

command / args / env

stdio: the server process to spawn

url / headers

http/sse: the remote endpoint

risk

the default tier for the server's tools. Omitted or invalid → destructive (always prompts). This is your trust declaration — the server's own annotations never drive the gate. Per-tool: /policy risk <tool> <tier> --save.

enabled: false

keep the entry, skip the connection

::trust rules

  • Remote tools always prompt until you lower their tier — a server's own "read-only" claim is never trusted.
  • Their results are untrusted input and pass through the injection quarantine.
  • Their outgoing arguments are scanned for secrets, and every remote call is a recorded egress event (/privacy egress); /privacy airgap on refuses them.
  • At the gate, an MCP tool's arguments render full-width — for a tool with no bespoke safety surface, the arguments are the safety surface.
  • ${VAR} in url, args, env, and headers expands from the environment or .env, so secrets never sit in the config file.

note

A server that misses connect_timeout at startup is reported and skipped; a call that exceeds call_timeout fails cleanly, and the engine discloses it like any other failed step.