Documentation · v0.2

MCPdef operations runbook

How to deploy, back up, verify, monitor, and debug a running mcpdef. Everything here describes the shipped OSS binary (crates/mcpdef, v0.1.x) — grounded in main.rs / listener.rs / gateway.rs. Config knobs are in CONFIG.md; the wire surface is in API.md.

Deploy #

MCPdef is one binary + one TOML file. No database, no sidecars.

State: what to back up #

FileConfig keyFormatLoss impact
Audit ledger[gateway] audit (default ./mcpdef-audit/audit.log)JSONL hash chain (append-only)Your tamper-evident record of every governed call. Back up continuously (it is the compliance artifact). Snapshot-friendly: append-only, safe to copy live.
Pin store[gateway] pins (optional)TOML, server → tool → sha256Your approved tool-definition baseline. Losing it means every tool re-pins on next start (trust-on-first-use) — a rug-pull window. Keep it in version control; it is deterministic and diff-clean by design.
Config + JWKSmcpdef.toml, [gateway.auth] jwks fileTOML / JSONThe policy itself. Version-control both.

[server.env] values (brokered upstream credentials) live in the config file — protect it like a secret store (file permissions, encrypted at rest, no world reads).

Durability note (honest guarantee): the ledger append is write + userspace flush, not fsync — a host crash can lose the last unsynced record(s). The hash chain proves integrity, not durability.

Audit-ledger verification #

Routine (proves internal consistency — any edit/delete of an interior record):

$ mcpdef audit verify --config mcpdef.toml
chain OK · 2 record(s) · head=f4bb388a0ba59cef9af31b5732c5f03c4dfb27d24e68fc078abafbe1a40fa17a

Exit is non-zero on a break, printing chain BROKEN at seq=<n>.

Plain verify cannot detect tail-truncation or wholesale replacement — a shortened-but-valid chain still verifies. To close that, periodically seal the (head, count) pair somewhere the same attacker cannot edit (a ticket, a separate WORM store), then verify against the seal:

# seal: record the current head hash and record count out-of-band, then later:
mcpdef audit verify --config mcpdef.toml --head <sealed-head-hash> --count <sealed-count>

--head/--count must be given together. A mismatch (fewer records, different head) fails verification even when the chain is internally consistent.

SIEM streaming from the free binary:

mcpdef audit tail --format ocsf -n 500 | your-siem-forwarder   # also: json | cef | syslog

Monitoring #

The audit ledger is the source of truth, and an optional read-only admin / observability server ([gateway.admin], off by default) exposes it for humans and scrapers on a separate port: Prometheus GET /metrics (mcpdef_tools_calls_total{server,tool,decision,rule}, a call-latency histogram, mcpdef_upstreams, mcpdef_uptime_seconds), a small JSON API (/api/v1/status|servers|stats|audit), and a built-in status UI at /. It carries no auth of its own — bind it to loopback or keep it behind your own network boundary. What to watch:

Troubleshooting (symptom first) #

What the client sees → why → what to do. Full gate semantics in API.md.

Symptom (exact error/status)CauseFix
Tool result isError:true: MCPdef denied: tool '<t>' is not on the allowlistServer has a tools = […] allowlist and <t> is not on itAdd the tool to [[server]] tools (or the profile), restart. Deliberate deny-by-default.
MCPdef denied: tool '<t>' matches deny pattern '<p>' …A deny glob (server or profile) matched; deny wins over allowRemove/narrow the glob if the tool is legitimate.
MCPdef denied: tool '<t>' is not in the gateway's active profile[gateway] profile (or --profile) scopes the whole surfaceRun without the profile override or extend [profile.<name>] tools.
MCPdef denied: no governed server exposes tool '<t>'No connected upstream listed that tool at connect timeCheck the upstream actually exposes it (tools/list is cached at connect — restart mcpdef after an upstream adds tools).
MCPdef denied: server '<s>' is not governed by MCPdefTool routed to a server with no policy entry (fail-closed)Add a [[server]] entry for it.
MCPdef denied: no role grants '<t>' on '<s>'RBAC: the token's scopes/roles hold no matching [[role]] grantGrant "<server-glob>:<tool-glob>" to a role the token carries, or fix the IdP scopes.
MCPdef denied: tool '<t>' on '<s>' changed since it was pinned (possible rug-pull)Pinned definition drifted (description/schema/annotations changed)Review with mcpdef diff-tools; if legitimate, re-approve with mcpdef pin. The tool is also hidden from tools/list until re-pinned.
MCPdef denied: global/tool rate limit exceeded … — retry shortlyToken bucket empty ([gateway.rate_limit])Retry with backoff; raise *_per_sec/*_burst if the budget is undersized.
MCPdef error: upstream '<s>' did not respond to '<t>' within <n>msupstream_timeout_ms fired; upstream wedged or slowCheck the upstream process/endpoint; raise the timeout for legitimately slow tools.
HTTP 401 + WWW-Authenticate: Bearer resource_metadata=…Auth on and the bearer is missing/invalid (bad signature, wrong aud/iss, expired, unknown kid, HMAC/none alg)Fetch the PRM document from the challenge URL, get a token from the advertised AS with aud = [gateway.auth] resource.
HTTP 403 origin "…" not allowedBrowser cross-site Origin (DNS-rebinding defense)Add the origin to [gateway] allowed_origins if it is your web app.
HTTP 405 on GET /mcpNo server→client SSE stream in this phaseExpected; use POST.
HTTP 413Request body > 2 MiB (MAX_BODY_BYTES)JSON-RPC messages are small by design; oversized tool args need a code change.
HTTP 503 + Retry-After: 1, gateway overloaded — retry shortlymax_inflight cap reached (deliberate shed, never an unbounded queue)Retry; raise max_inflight or add replicas if sustained.
HTTP 404 on /.well-known/oauth-protected-resource[gateway.auth] disabledExpected when auth is off.
Startup: mcpdef: warning — [gateway.auth] is enabled but applies only to the HTTP listenerRunning stdio with auth configuredUse --http; stdio has no per-request identity to authenticate.
Startup fails: upstream '<id>' did not complete its initialize handshake within <n>msUpstream wedged at connectFix the upstream; the same upstream_timeout_ms bounds connect.
Startup fails: N validation error(s) in mcpdef.tomlStructural config problemRun mcpdef validate — it lists every error (unknown transport, missing url/command/wasm, dead rate buckets, incomplete auth…).
Sandboxed call fails — HTTP 500 gateway error: … / CLI error naming e.g. all fuel consumed or an epoch deadlinewasm_fuel / wasm_deadline_ms / wasm_max_memory_mb exceeded (a trap)Raise the specific cap. Note: trap-failed calls are not audited today (denials/timeouts are).
Sandboxed component's outbound connect fails (error text depends on the guest)The destination is not in wasm_allow_egress (default deny-all), or is in an always-blocked classAdd the exact ip:port to wasm_allow_egress (metadata/special-use ranges are rejected even if listed; hostnames not accepted).
mcpdef pin fails: `mcpdef pin` needs a pin store[gateway] pins unsetSet pins = "./mcpdef-pins.toml".
mcpdef audit verify fails: audit ledger … does not existFresh install — the ledger is written on first governed callExpected before first run.

Security posture #