Documentation · v0.2

Architecture

MCPdef is one static binary in the data path. Every box below is a crate inside that binary; nothing runs beside it. A call comes in on the left, is decided on the way through, and is recorded whether it is allowed or not.

MCPdef architecture — the governed path Architecture diagram of MCPdef: an agent reaches one open-source binary through stdio or Streamable HTTP front ends and OAuth bearer authentication, a gateway loop routes each tool call through policy, pin, rate-limit and injection-scan gates to a transport or a Wasmtime sandbox and on to upstream servers, and every outcome is written to a hash-linked audit ledger that can be tailed to a SIEM. MCPDEF · ONE BINARY · APACHE-2.0 PRINCIPAL ALLOW EVERY OUTCOME SEAM MCP Agent MCP client Claude · IDE orchestrator IN stdio front mcpdef run · NDJSON IN Streamable HTTP POST /mcp loopback · Origin 403 503 cap · 2 MiB body AUTH mcpdef-auth OAuth 2.1 RS bearer JWT vs JWKS RFC 9728 challenge GW Gateway loop answers initialize tools/list · ping routes tools/call forwards the rest Principal → RBAC + audit identity GATE 1 mcpdef-policy allowlist · profiles RBAC · arg rules GATE 2 mcpdef-pin tool-def hash pins drift ⇒ hide + deny GATE 3 mcpdef-ratelimit token buckets per-tool + global GATE 4 mcpdef-inspect injection + secrets off · warn · enforce LEDGER mcpdef-audit append-only JSONL hash-linked · verify WASM mcpdef-sandbox Wasmtime · wasip2 WIT fuel · memory · epoch egress allowlist XPORT mcpdef-transport stdio child env cred injection HTTP + SSE bridge Last-Event-ID resume egress/SSRF guard DNS-pinned CORE mcpdef-core JSON-RPC 2.0 envelope + Decision types — shared by every crate SRV Upstream MCP server stdio child SRV Upstream MCP server remote HTTP FILE Wasm module or component run in-process SIEM SIEM forwarder audit tail
  • Focal · gateway, ledger
  • Crate / stage
  • Shared foundation
  • Agent
  • External
  • Governed path
  • Agent transport

Read left to right: a call enters through a front end, is authenticated, is decided by the gates in order, and only then reaches a transport or the sandbox. Every outcome is recorded. Model traffic never crosses MCPdef — it governs the agent-to-tool wire only.

The path of one tools/call

  1. Front ends. A client speaks stdio (mcpdef run) or Streamable HTTP (mcpdef run --http / mcpdef up). The HTTP listener binds loopback by default, validates Origin (a cross-site request gets 403), sheds load at an optional in-flight cap (503) and caps request bodies at 2 MiB.
  2. Auth (HTTP only). mcpdef-auth validates the per-request bearer JWT against a JWKS and serves the RFC 9728 metadata and challenge. The resulting principal becomes the audit identity and the input to RBAC. A stdio client is a local process and skips this step.
  3. Gateway loop. Answers initialize, tools/list and ping itself, routes tools/call through the gates, and forwards everything else to the upstream.
  4. Gates, in order. mcpdef-policy (deny-by-default allowlist and profiles, then RBAC role grants and per-argument rules) → mcpdef-pin (a tool whose definition drifted from its pin is hidden and denied) → mcpdef-ratelimit (token buckets, per tool and global) → mcpdef-inspect (scans tool descriptions at connect, and results per call, for injection and secret exfiltration). A refusal is an MCP tool-error the model can read and correct.
  5. Transport or sandbox. mcpdef-transport reaches a stdio child (credentials injected through [server.env]), a Streamable-HTTP server, or a legacy HTTP+SSE server, behind an egress/SSRF guard with DNS pinning. mcpdef-sandbox sits behind the same transport seam and runs an untrusted .wasm server in-process under Wasmtime, so every gate applies to it unchanged.
  6. Ledger. The gateway appends every outcome to the append-only, hash-linked ledger in mcpdef-audit; mcpdef audit tail exports it to a SIEM as OCSF, CEF, syslog or JSON. mcpdef-core — the JSON-RPC 2.0 envelope and decision types — is shared by every crate.

Gate order, and what the client sees

Every outcome, allow or deny, appends one record to the ledger. A denial comes back as a tools/call result with isError: true and the text MCPdef denied: <reason>, never as a protocol error, so an agent can adjust and retry.

#GateCrateAudit rule on deny
1Routing — is the tool exposed by any governed server?mcpdefunknown-tool
2Allowlist and profilesmcpdef-policyunknown-server, deny-glob, not-on-allowlist, not-in-active-profile
3RBAC (authenticated HTTP callers, when [[role]] is defined)mcpdef-policyrbac
4Pin / rug-pull (when [gateway] pins is set)mcpdef-pinrug-pull
5Rate limit — after authorization, so denied floods cannot drain bucketsmcpdef-ratelimitrate-limited
6Dispatch and per-call timeoutmcpdef-transportupstream-timeout

The injection scan in mcpdef-inspect runs in off, warn or enforce mode; a finding is audited under an injection or secret-exfil rule. The exact client-visible messages for each row are in the API reference.

The sandboxed path

An upstream can be a .wasm server that MCPdef runs itself. It is one more kind of upstream behind the transport seam, so the same gates decide first.

  1. The client sends tools/call. On the HTTP listener the bearer JWT is validated for this request.
  2. The allowlist, RBAC, pin and rate-limit gates run. If any denies, the record is appended and the client gets the tool-error; the sandbox is never entered.
  3. If allowed, the call goes to the sandbox — transport = "wasm" (core module) or "wasm-component". The instance is reused, runs on the blocking pool, and its fuel and epoch deadline are re-armed for each call.
  4. A component that opens an outbound TCP connection is checked against the egress allowlist: permitted only if the destination is allowlisted and passes the same IP classification as the main egress guard. A core module has an empty linker, so this step never applies to it.
  5. The response returns to the gateway, a record is appended, and the client gets the governed response.
What is and is not in the ledger A sandbox trap — out of fuel, over the memory cap, past the wall-clock deadline — is a transport error, not a policy denial. It reaches the client as an error, and such trap-failed calls are not appended to the ledger today. Denials, timeouts and completed calls are.

Boundaries

Crate reference

CrateRole
mcpdefThe binary: config, the gateway loop, the Streamable HTTP listener, and the CLI (run, up, call, validate, audit, pin).
mcpdef-coreThe normalized JSON-RPC 2.0 envelope and the MCP method and decision types.
mcpdef-transportThe Transport trait; stdio child with credential injection; Streamable HTTP and the legacy HTTP+SSE bridge; the egress/SSRF guard.
mcpdef-sandboxRuns an untrusted MCP server under Wasmtime behind the same Transport seam, with fuel, memory and optional wall-clock limits.
mcpdef-policyThe deny-by-default allowlist with globs and named profiles, the RBAC role-to-grant model, and per-agent and per-argument rules.
mcpdef-authThe OAuth 2.1 resource server: per-request bearer JWT validation against a JWKS, asymmetric-only, RFC 8707/9068 audience, RFC 9728 metadata.
mcpdef-pinCanonical tool-definition hashing and a persistent pin store, for rug-pull detection.
mcpdef-ratelimitToken-bucket limits, per tool and global, for the tools/call hot path.
mcpdef-inspectInjection and secret-exfil scanning over tool descriptions (at connect) and results (per call).
mcpdef-auditThe append-only, hash-linked ledger, offline verify, and OCSF, CEF and syslog export.

Next: the configuration reference for every key that drives the boxes above, and the operations runbook for verifying the ledger.