Configure MCP servers

Attach third-party tools to Cantrip via the Model Context Protocol.

What MCP gives you

MCP is the cross-vendor standard (Claude Code, Cursor, Codex, Copilot) for exposing tools to an agent over a wire protocol. A server publishes tool descriptors and handles invocations; Cantrip is the client. Configured servers appear alongside Cantrip’s built-in tools as mcp__<server>__<tool> — the agent calls them the same way it calls read_file or juju_status.

Useful MCP servers for a Juju-charm agent: Charmhub search, Grafana PromQL/LogQL wrappers, GitHub org context, Launchpad bug lookup, workload-specific introspection. See design/MCP_SERVERS.md if you want to author one.

Where to configure

Cantrip reads two YAML files, in order:

  1. User scope: ~/.config/cantrip/mcp.yaml (override via CANTRIP_MCP_USER_CONFIG) — the right place for personal servers you want across every charm.
  2. Repo scope: cantrip.mcp.yaml next to your charm directory — for project-specific servers that should be committed. Repo-scope values override user-scope on a name collision.

A malformed file is logged and skipped rather than crashing the agent, so a typo in one place never takes the other down.

Add a stdio server

Stdio is the common case — Cantrip spawns the server as a subprocess and talks to it over pipes. Example cantrip.mcp.yaml:

servers:
  charmhub:
    command: uvx
    args: ["charmhub-mcp"]
    env:
      CHARMHUB_TOKEN: ${CHARMHUB_TOKEN}
    timeout_seconds: 30
    allowed_tools: ["search", "info"]

Every field is validated at load time:

Cantrip auto-starts every configured server on boot. Failures land in per-server status (see /mcp) so one broken server never blocks the healthy ones.

Add an HTTP server

For remote servers, use the streamable-HTTP transport:

servers:
  grafana:
    transport: http
    url: https://grafana.example.com/mcp
    headers:
      Authorization: "Bearer ${GRAFANA_TOKEN}"
    timeout_seconds: 60

url is required for HTTP. headers are sent with every request. For servers that require OAuth rather than a pre-shared token, see OAuth.

Inspect running servers with /mcp

Type slash commands directly in the chat (TUI or Web):

# Overview of every configured server:
/mcp

# Per-server tool list, with descriptions:
/mcp tools charmhub

# Full syntax help:
/mcp help

Status markers:

OAuth 2.1 for HTTP servers

Servers that require a user login use the SDK’s OAuthClientProvider. Cantrip handles PKCE, dynamic client registration, RFC 9728 Protected Resource Metadata discovery, and token refresh — you just declare the server:

servers:
  charmhub-oauth:
    transport: http
    url: https://charmhub.example.com/mcp
    oauth:
      client_name: cantrip-prod
      scopes: ["repo", "publish"]
      redirect_port: 9876

On first connection Cantrip opens the authorisation URL in your browser (via webbrowser.open) and binds a single-purpose listener to http://127.0.0.1:<redirect_port>/callback. The listener captures one OAuth redirect, returns the code and state to the SDK, and shuts down. Successful authorisations store the tokens at ~/.config/cantrip/mcp_tokens/<server>/ (override with CANTRIP_MCP_TOKEN_DIR) at 0600 so only your user can read them.

Want encryption at rest? Set CANTRIP_MCP_GPG_TOKENS=1 and Cantrip wraps every write in gpg --symmetric. You need a configured gpg-agent so writes don’t block on a passphrase prompt.

If the OAuth provider uses its own published metadata document (some servers don’t support dynamic client registration), add client_metadata_url: to the oauth block.

Mid-task elicitation

Some MCP servers can pause a tool call to ask the user for structured input — “which database name should I create?”, “paste the verification code from your email”. Cantrip bridges these through the UI event bus. When a server requests elicitation:

  1. Cantrip emits an MCP_ELICITATION_REQUEST event.
  2. The TUI / Web renders the prompt.
  3. Your answer flows back through complete_mcp_elicitation.

A bounded 600-second timeout auto-declines runaway requests so a server can’t park the conversation forever. Interactive form rendering in the TUI/Web is still a work-in-progress — the event is emitted today; the prompt widget is a follow-up.

Discover servers via marketplaces

A marketplace is a catalogue of available MCP servers. Cantrip reads marketplace.json files from three source kinds, declared at the top level of cantrip.mcp.yaml:

marketplaces:
  # Fetch from a GitHub repo's main branch:
  - github: anthropic-ai/mcp-servers

  # Read from a local directory (offline / corporate mirrors):
  - directory: ~/work/canonical-mcp-catalog

  # Arbitrary URL:
  - url: https://example.com/marketplace.json

List what’s available:

# Show every server from every configured marketplace:
/mcp marketplace

# Bypass the 24-hour cache and re-fetch:
/mcp marketplace refresh

Each entry shows its description, install hint, required env vars, and OAuth scopes. Cantrip never auto-installs a server — you copy the descriptor into your own cantrip.mcp.yaml after reviewing it. This is intentional: marketplaces are discovery surfaces, not trust surfaces.

Override the marketplace cache directory with CANTRIP_MCP_MARKETPLACE_CACHE. The default cache sits at ~/.cache/cantrip/marketplaces/ with a 24-hour TTL.

Security notes

Troubleshooting

“Server shows [!!] failed”

Run /mcp to see the failure reason. Common causes:

“Tools from a connected server aren’t visible to the agent”

Check the per-server allowed_tools list — a non-empty list filters the exposed set. Empty means “allow every tool the server publishes”. After editing the YAML restart Cantrip; tool registration happens at boot.

“OAuth hung on the redirect”

The localhost listener waits up to 5 minutes for the browser callback. Check that a browser tab actually opened (webbrowser.open silently fails on some headless systems — Cantrip logs the authorisation URL so you can copy it manually). Re-running the connection after a failure starts a fresh flow.