Guide

MCP Integration

Ze includes an MCP (Model Context Protocol) server that lets AI assistants control BGP operations. The server runs inside the daemon and wraps the same command dispatcher used by the CLI and web interface.

Starting the MCP Server

CLI flag:

ze start --mcp 9718
ze --mcp 9718 config.conf

Config file:

environment {
    mcp {
        enabled true;
        server main {
            ip 127.0.0.1;
            port 9718;
        }
    }
}

Environment variable:

export ze_mcp_listen=127.0.0.1:9718
# or simply:
export ze_mcp_enabled=true  # defaults to 127.0.0.1:8080

Precedence: CLI > environment variable > config file.

The MCP server binds to 127.0.0.1 only. See remote-access.md for accessing it from other machines.

Authentication

MCP supports four authentication modes selected by environment.mcp.auth-mode:

Mode Use case Config
none Loopback dev / tunnel-only deployments
bearer Single shared secret, one trusted caller token leaf
bearer-list Per-identity tokens, many callers, scope-scoped sessions identity[] list
oauth OAuth 2.1 resource server, external AS manages identities oauth container + TLS

Identity is bound at initialize and carried on the session for its lifetime; subsequent requests with the assigned Mcp-Session-Id header are trusted by session-id validity alone.

bearer (legacy single token)

environment {
    mcp {
        enabled true;
        auth-mode bearer;
        token my-secret-token;
        server main { ip 127.0.0.1; port 9718; }
    }
}

Env var ze.mcp.token and CLI flag --mcp-token still work. The token leaf is ze:sensitive -- masked in show config output. A token set without an explicit auth-mode infers bearer for operators upgrading from pre-Phase-2 configs.

bearer-list (per-identity tokens)

environment {
    mcp {
        enabled true;
        auth-mode bearer-list;
        identity alice { token alice-token; scope [ mcp.read mcp.write ]; }
        identity bob   { token bob-token;   scope [ mcp.read ]; }
        server main { ip 127.0.0.1; port 9718; }
    }
}

Each identity's token is compared constant-time. The matching entry's name + scopes ride on the session. Add / remove / rotate identities independently.

oauth (OAuth 2.1 resource server)

environment {
    mcp {
        enabled true;
        bind-remote true;
        auth-mode oauth;
        oauth {
            authorization-server https://auth.example/;
            audience             https://mcp.example/;
            required-scopes      [ mcp.admin ];
        }
        tls {
            cert /etc/ze/mcp.pem;
            key  /etc/ze/mcp.key;
        }
        server main { ip 0.0.0.0; port 443; }
    }
}

Tokens are validated locally: RS256 / RS384 / RS512 / ES256 / ES384 signatures are verified against JWKS fetched from the authorization server's RFC 8414 metadata document. HS* (HMAC) and alg: none are always rejected. iss / aud / exp / nbf / scope claims are validated with 60 s leeway.

ze config validate rejects internally inconsistent configurations (oauth without TLS on a remote bind, oauth without authorization-server, bind-remote without auth, etc.) before the daemon starts. See rules/exact-or-reject.md for the contract.

RFC 9728 metadata: when auth-mode oauth, the server publishes /.well-known/oauth-protected-resource listing the authorization server(s) and supported scopes. Clients discover the AS through this URL when they hit a 401.

Constant-time comparison

Bearer tokens (both bearer and bearer-list) use subtle.ConstantTimeCompare so response timing does not reveal which entry matched (or whether any did). The bearer-list scan visits every entry regardless of early match.

Protocol

The MCP server speaks JSON-RPC 2.0 over HTTP. Each request is a POST with a JSON-RPC body. The server implements the MCP tool-calling protocol:

  1. Client sends initialize to start a session
  2. Client calls tools/list to discover available tools
  3. Client calls tools/call with tool name and arguments

Tools

All MCP tools are auto-generated from the YANG command registry at tools/list time. Each command group (e.g. rib, show config, metrics) becomes a tool with an action enum listing its subcommands. When a new YANG command is registered, it appears as an MCP tool automatically without code changes.

Run tools/list against a live daemon to see the current tool inventory.

A small set of handcrafted tools (ze_execute, ze_commands, ze_reference) provide escape-hatch and discovery capabilities alongside the generated tools.

ze_execute

Run any command the CLI supports. Use ze_commands to discover available commands.

Parameter Type Required Description
command string Full command string

ze_commands

No parameters. Returns the list of all registered daemon commands.

ze_reference

No parameters. Returns the full machine-readable reference for this daemon (CLI commands, daemon API endpoints with dispatch keys, plugins, address families, config services) as JSON. This is the same data as ze help ai --json, assembled from internal/component/aihelp so the CLI and MCP never diverge. An MCP client sees this tool in tools/list on connect, so it can discover the instance's capabilities without out-of-band documentation.

AI Help Reference

ze help ai generates a machine-readable reference from the running binary. All data comes from the plugin registry, YANG schemas, and RPC registrations, so it is never out of date.

Command Content
ze help ai Summary with counts and quick start
ze help ai --json Machine-readable JSON with commands, RPCs, plugins, families, services
ze help ai cli CLI subcommands (ze bgp, ze config, ...)
ze help ai api Daemon API commands with parameters (YANG RPCs)
ze help ai mcp MCP tools with parameters and examples
ze help ai dispatch Dispatch keys for daemon commands
ze help ai all Everything

The legacy flag form (ze help --ai --api) is still accepted as a hidden alias.

Agent Tooling

Offline commands for agent-driven config validation and repair:

Command Purpose
ze config validate --json <file> Structured diagnostics with stable codes
ze explain [--json] <code> Explain a diagnostic code
ze config fix --plan --json <file> Plan-only repair candidates
ze skills list [--json] List bundled agent skills
ze skills get <name> [--full] Load version-matched skill content

These commands do not require a running daemon. Agents use them to validate and fix config before committing.

Testing

ze-test mcp is an MCP client for functional tests. It reads commands from stdin and sends them to the MCP endpoint.

# Start daemon with MCP
ze --mcp 8080 config.conf &

# Send commands
echo 'wait-established
peer * update text origin igp next-hop 1.1.1.1 nlri ipv4/unicast add 10.0.0.0/24' | ze-test mcp --port 8080

Special stdin directives:

Directive Description
# comment Ignored
wait <duration> Pause (e.g. wait 2s)
wait-established Poll until a BGP peer is Established
@tool_name {json} Call a specific MCP tool with JSON arguments
<command> Run via ze_execute
task-call <tool> <json> Call tool with task:{}, print taskId
task-get <id> Get task status
task-result <id> Get completed task result
task-cancel <id> Cancel a running task
task-list List task IDs for current identity
task-wait <id> <state> Poll until task reaches state
sse-listen Open the standalone GET /mcp SSE stream (server-initiated frames)
sse-expect <method> Wait for a server-initiated frame with that JSON-RPC method, print it

$LAST substitutes the most recent directive output (e.g., taskId from task-call). Pass --tasks to declare capabilities.tasks={} at initialize.

Example using typed tools:

wait-established
@ze_announce {"family":"ipv4/unicast","origin":"igp","next-hop":"1.1.1.1","prefixes":["10.0.0.0/24"]}
@ze_peers {}