Demo: Mitrity Gateway
Run a self-contained Docker demo that exercises every governance capability through a realistic, scripted scenario. A Claude Agent SDK agent, wired to MITRITY with the mitrity adapter, works through the scenario while Mitrity Gateway governs both of the agent's entrances:
- MCP tools (
fs__read_file,shell__run_command,api__call_api, …) reach the model through the gateway, which the SDK starts as its MCP server. Everytools/callis evaluated against your policies before the upstream tool sees it. - The SDK's own built-in tools (
Bash,Write,Edit) never produce an MCP call. The adapter installs aPreToolUsehook that admits each one through the gateway's loopback admission API before the SDK runs it — same rules, same command analysis, same DLP, same audit trail, stampedsurface=agent_hook. If the gateway cannot be reached, the call is denied.
The demo takes about 15 minutes and requires only Docker Desktop and API keys.
Looking for the sidecar version? See Demo: Mitrity MCP Sidecar for the single-upstream variant.
Want to see multi-agent delegation chains? See Demo: Multi-Agent Governance for a three-container compose stack exercising real agent-to-agent delegation, per-agent threat intel, and privilege boundaries.
Prerequisites
- Docker Desktop (or any Docker runtime)
- A MITRITY account with an active tenant
- An Anthropic API key for the Claude agent
Step 1: Configure your dashboard
Before running the demo, set up three things in the MITRITY dashboard:
Register an agent
Go to Agents > Register Agent and create an agent:
- Name:
demo-agent - Mission scope: "Workspace file management and system operations"
Copy the Agent ID (UUID). Also copy the Agent Key (ak_...). Save it — it cannot be retrieved later.
Create policies
Tool patterns name their surface. An MCP tool served by the gateway is mcp:<namespace>__<tool> — the upstream's namespace and the tool name joined by a double underscore (mcp:fs__read_file) — and one of the SDK's built-in tools is builtin:<tool> (builtin:bash). Create the following policies to see the full range of governance decisions:
| Policy | Type | Pattern | Scope |
|---|---|---|---|
| Allow workspace reads | allow | mcp:fs__read_file, mcp:fs__list_directory | path starts with /workspace |
| Allow workspace writes | allow | mcp:fs__write_file | path starts with /workspace |
| Allow safe commands | allow | mcp:shell__run_command | resolved commands ls, pwd, cat, echo, whoami |
| Block system files | deny | mcp:fs__read_file, mcp:fs__delete_file | path outside /workspace |
| Block destructive commands | deny | mcp:shell__run_command | resolved commands rm, curl, wget, nc, chmod |
| Block dangerous SQL | deny | mcp:api__query_database | contains DROP, DELETE, TRUNCATE |
| Hold production deploys | hold | mcp:api__call_api | url contains "production" |
| Block destructive built-ins (phase 8) | deny | builtin:bash | resolved commands rm, curl, wget, nc, chmod |
Also enable these global settings:
- Prompt injection detection
- DLP with PII and credential patterns — DLP applies to
builtin:writecontent with no extra rule
Note: The gateway serves a namespaced tool as
<namespace>__<tool>(fs__read_file), the one form the model APIs can call, and a rule targets it asmcp:<namespace>__<tool>. A pattern without a surface prefix matches nothing, and the control plane rejects one when a policy is created or updated. The full grammar is in Writing policies.Created policies for an earlier version of this demo? Tools used to be served as
fs:read_file,shell:run_command,api:call_api. Recreate the policies above withmcp:<namespace>__<tool>patterns and add thebuiltin:bashrule for phase 8.
Optional: route built-in Bash into the governed shell (phase 9)
Set the policy knob builtin_exec_routing: governed_shell on the agent's policy. Phase 9 also needs a gateway from edge release v0.21 or later — the release that ships the governed shell — with the exec tool source enabled and the mitrity-hook binary inside the container. The demo image pulls ghcr.io/mitrity-io/mitrity-mcp-gateway:latest; until that tag carries the governed shell, phase 9 prints what it will demonstrate and skips. Phases 1–8 do not depend on any of this.
Step 2: Clone and configure
git clone git@github.com:mitrity-io/iag-demo-mcp-gateway.git
cd iag-demo-mcp-gateway
cp .env.example .env
Edit .env with your values:
ANTHROPIC_API_KEY=sk-ant-... # Anthropic API key for Claude
MITRITY_AGENT_KEY=ak_... # Agent key from dashboard
MITRITY_CONTROL_PLANE_URL=https://api.mitrity.com
MITRITY_AGENT_ID=<uuid> # Agent UUID from dashboard
Step 3: Run the demo
docker compose up --build
The container builds locally — it pulls the Mitrity Gateway binary from ghcr.io and installs the adapter from PyPI, pinned to mitrity[claude-agent-sdk]==0.2.0 — then runs the scenario. Watch your terminal for the output. To try another adapter release, pass the pin as a build argument: docker compose build --build-arg MITRITY_PYTHON_SPEC='mitrity[claude-agent-sdk]==X.Y.Z'.
What the demo does
The scenario runs eight phases, numbered 1–6, 8 and 9. There is no phase 7 here: delegation chains and threat intelligence are the multi-agent demo, which kept the number.
Phase 1 — Normal Operations. The agent reads workspace files, writes summaries, lists directories, runs safe shell commands, and calls APIs. All actions pass through the gateway and are allowed by your policies.
Phase 2 — Policy Violations. The agent attempts to read /etc/passwd, run rm -rf, execute curl, and issue a DROP TABLE query. The gateway blocks each action and returns the policy reason.
Phase 3 — Prompt Injection. The agent processes "user input" that contains embedded injection payloads. The gateway's injection detection engine catches and blocks the attacks before any tool executes.
Phase 4 — DLP & Data Protection. The agent tries to write files containing API keys and AWS secrets, send notifications with PII (SSNs, credit card numbers), and include credentials in API calls. The gateway's DLP engine blocks data exfiltration.
Phase 5 — Escalation & Hold. The agent attempts a production deployment. The gateway's hold policy pauses the action and creates an approval request visible in your MITRITY dashboard.
Phase 6 — Credential Broker + Hot Rotation. The agent calls api__connect_database with ${credential:demo_db_password} in the connection string. The gateway resolves the placeholder via the broker; the upstream tool returns the password hash. Mid-phase you rotate the credential in the dashboard — the next call picks up the new value within 30 seconds with no agent or gateway restart. A final sub-step exercises the fail-closed path with an unknown credential id. Requires creating a demo_db_password credential definition + grant in your tenant before running (see Credentials docs).
Phase 8 — Built-in Tools (admission hook). The agent uses the SDK's own Bash and Write tools instead of the MCP tools. An allowed ls runs; rm -rf is denied with the policy reason; the same command hidden behind bash -c '…' is still denied, because the edge parses the command tree rather than matching the first word; a .env file full of AWS and Stripe keys written with Write is blocked by DLP before it exists. Every decision is an audit row with surface=agent_hook.
Phase 9 — Governed Shell. With builtin_exec_routing: governed_shell, an allowed built-in Bash is not executed by the SDK at all: the hook rewrites it to mitrity-hook exec <ticket>, the gateway runs the judged bytes in its own sandbox, and egress to a host outside the agent's destination allowlist is refused by the governed shell's proxy. The audit event carries execution_result.sandbox_mode and the egress counters. Requires edge release v0.21 or later; on an older gateway the phase prints what it demonstrates and is skipped.
What to look for
While the demo runs, open your MITRITY dashboard to see:
- Audit Log: Every tool call with its governance decision, latency, and policy match. Phase 8 and 9 rows carry
surface=agent_hook; phase 9 rows also carry the sandbox mode the command ran under - Agent Detail: The demo agent's activity timeline, drift score, and Execution coverage card — fed by what the runtime attested at startup (which built-in tools are hooked, which are not, which other MCP servers are configured) and corroborated by the admission decisions the gateway actually saw
- Approval Requests: The held production deployment waiting for your approval (Phase 5)
The terminal prints the attestation when the session starts and, at the end, a summary with counts of allowed, blocked, and held actions plus the admission hook's own tally: built-in calls admitted, allowed, denied, held, blocked because the edge could not be reached, and routed to the governed shell.
Architecture
The entire demo runs inside a single Docker container:
Docker Container
└── Python scenario runner (Claude Agent SDK)
├── built-in tools: Bash, Write, Edit ── PreToolUse hook (mitrity adapter)
│ └── admission API (unix:/run/mitrity/admission.sock)
└── MCP server "mitrity" = Mitrity Gateway (stdio)
├── upstream "filesystem" (namespace fs): fs__read_file, fs__write_file, fs__list_directory, fs__delete_file
├── upstream "shell" (namespace shell): shell__run_command
└── upstream "api" (namespace api): api__call_api, api__query_database, api__send_notification, api__connect_database
One gateway process serves both entrances: the MCP tools/call stream from the SDK and the loopback admission API the hook calls. It connects to your MITRITY control plane over HTTPS for policy evaluation, event reporting and heartbeat, and attests the runtime's posture — which built-in tools are hooked, which are not, other MCP servers, the permission mode — so the agent's coverage badge reflects what is actually governed. Filesystem and shell tools execute for real against the container's /workspace directory. API tools return mock responses.
The adapter is mitrity.claude_agent_sdk.Governor from the mitrity package on PyPI (pip install 'mitrity[claude-agent-sdk]'). scenario/runner.py shows the few lines that wire it up: the gateway is declared as the SDK's MCP server, the hook is installed on the built-in tools, and the SDK's permission callback allows everything MITRITY allows. To govern your own Claude Agent SDK or Claude Code agent the same way, see Installing the hook.
Environment variables
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY | Yes | Anthropic API key for Claude |
MITRITY_AGENT_KEY | Yes | Agent key from dashboard |
MITRITY_CONTROL_PLANE_URL | Yes | Control plane URL |
MITRITY_AGENT_ID | Yes | Agent UUID from dashboard |
MITRITY_DEMO_SPEED | No | normal (default) or fast (skip pauses) |
ANTHROPIC_MODEL | No | Claude model (default: claude-sonnet-5) |
MITRITY_DEMO_FORCE_PHASE9 | No | 1 runs phase 9 even when the gateway does not advertise the governed shell |
Troubleshooting
- Every built-in tool call is denied with "MITRITY could not authorize this action" — the hook could not reach the gateway's admission API. The gateway logs its startup on stderr; it refuses to start when
/run/mitrityis not owned by the container user or is group- or world-writable (the image creates it asdemo, mode 0700). - Phase 8's
rm -rfruns — thebuiltin:bashdeny rule is missing from the agent's policy. - An MCP tool call is not decided by the rule you expect — check that the pattern carries its surface prefix and the namespace (
mcp:fs__read_file, notfs__read_fileormcp:fs:read_file). The decision reason in the demo output and the audit event name the rule that matched. - Phase 9 is skipped — the gateway in the image is older than edge release v0.21. Phase 9 also needs
builtin_exec_routing: governed_shellon the agent's policy; without it an admittedBashis run by the SDK, as in phase 8.
Customization
- Policies: Modify policies in the MITRITY dashboard and re-run the demo to see different behaviors
- Workspace: Mount your own files via
volumesindocker-compose.yml. The container runs as the unprivileged userdemo(uid 1000); on a Linux host, make the mounted directory writable by that uid, or setuser:indocker-compose.ymlto match its owner - Scenarios: Edit files in
scenario/phases/to add custom test prompts
Next steps
- Demo: Mitrity MCP Sidecar — run the same scenario with the lightweight sidecar wrapper
- Demo: Multi-Agent Governance — three-container stack exercising real delegation chains, per-agent threat intel, and privilege boundaries
- Governance coverage — what the hook covers per framework, and how the coverage badge is computed
- Governed shell — the sandboxed execution path behind phase 9
- Integration modes — understand when to use Gateway vs Sidecar
- Writing policies — create your own governance rules
- Configuration reference — full gateway YAML reference