Demo: Mitrity Gateway
This self-contained Docker demo shows every governance capability at work in a realistic, scripted scenario. A Claude Agent SDK agent, connected to MITRITY with the mitrity adapter, works through the scenario. Mitrity Gateway governs both of the agent's entrances, that is, both ways the agent can act:
- MCP tools (
fs__read_file,shell__run_command,api__call_api, …): the SDK starts the gateway as its MCP server, and the model reaches these tools through it. The gateway evaluates everytools/callagainst your policies before the upstream tool sees it. - The SDK's own built-in tools (
Bash,Write,Edit): these never produce an MCP call. Instead, the adapter installs aPreToolUsehook. Before the SDK runs a built-in tool, the hook admits it through the gateway's loopback admission API. The tool gets the same rules, command analysis, DLP and audit trail as an MCP tool, and its events are stampedsurface=agent_hook. If the gateway cannot be reached, the call is denied.
The demo takes about 15 minutes. You only need Docker Desktop and API keys.
Looking for the sidecar version? Demo: Mitrity MCP Sidecar is the single-upstream variant.
Want to see multi-agent delegation chains? Demo: Multi-Agent Governance runs a three-container compose stack with 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 you run the demo, set up the following in the MITRITY dashboard.
Register an agent
- Go to Agents > Register Agent and create an agent with these values:
- Name:
demo-agent - Mission scope: "Workspace file management and system operations"
- Name:
- Under Intent Policy, select Create Policy, enter a name such as
demo-policyand select Create. The new policy has allow-all, fail-open defaults, with Prompt Injection Detection and Data Leakage Protection on Enforce. The next section adds the demo's rules to it. - Select Register Agent.
- Copy the Agent Key (
ak_...) that the dialog shows. Save the Agent Key: it cannot be retrieved later. Then copy the Agent ID (a UUID) from the agent's detail page.
Add the policy rules
Each tool pattern starts with its surface, which says how the tool is reached:
- 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). - One of the SDK's built-in tools is
builtin:<tool>(builtin:bash).
Add the following rules to the agent's policy, to see the full range of governance decisions. Open the policy in /app/policies and select Add Rule for each row: Type is the rule's action, Pattern its tool pattern, and Scope the constraint the rule checks.
| Rule | 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 |
These two features of the same policy must be on (Enforce):
- Prompt Injection Detection;
- Data Leakage Protection, with PII and credential patterns (see DLP Pattern Authoring). DLP also applies to
builtin:writecontent, with no extra rule.
Note: The gateway serves a namespaced tool as
<namespace>__<tool>(fs__read_file), because that is the one form the model APIs can call. A rule targets it asmcp:<namespace>__<tool>. A pattern without a surface prefix matches nothing, and the control plane rejects it when you create or update a policy. 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_commandandapi:call_api. Recreate the rules above withmcp:<namespace>__<tool>patterns, and add thebuiltin:bashrule for phase 8.
Optional: route built-in Bash into the governed shell (phase 9)
To run 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;
- the
exectool source enabled on that gateway; - the
mitrity-hookbinary inside the container; - a shell and, on Linux,
bubblewrapwhere the gateway runs, as the governed shell page lists.
The demo image does not provide the last three. It copies the gateway binary from ghcr.io/mitrity-io/mitrity-mcp-gateway:latest into a python:3.12-slim image whose gateway configuration leaves the exec tool source off, and it carries neither mitrity-hook nor bubblewrap. Phase 9 checks only the gateway's version, and the gateway in that tag is newer than v0.21, so the phase runs:
- with
builtin_exec_routing: governed_shellon the policy, the gateway refuses each built-inBashcall withrouting_unavailable, because it serves no governed shell; - without it, the SDK runs
Bashas in phase 8.
Phases 1–8 do not depend on any of this.
Step 2: Clone and configure
Clone the demo repository and create your .env file:
git clone git@github.com:mitrity-io/iag-demo-mcp-gateway.git
cd iag-demo-mcp-gateway
cp .env.example .env
Then 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. The build pulls the Mitrity Gateway binary from ghcr.io and installs the adapter from PyPI, pinned to mitrity[claude-agent-sdk]==0.2.0. Then the scenario runs, and you can watch its output in your terminal.
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 have their own 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 your policies allow them.
-
Phase 2, Policy Violations: the agent tries to read
/etc/passwd, runrm -rf, executecurland issue aDROP TABLEquery. 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 that contain 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 the 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, which you can see in your MITRITY dashboard.
-
Phase 6, Credential Broker + Hot Rotation: this phase needs a
demo_db_passwordcredential definition and a grant for it in your tenant. Create both before you run the demo (see the Credentials docs). In this phase:- The agent calls
api__connect_databasewith${credential:demo_db_password}in the connection string. - The gateway resolves the placeholder through the broker, and 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.
- The agent calls
-
Phase 8, Built-in Tools (admission hook): the agent uses the SDK's own
BashandWritetools instead of the MCP tools.- An allowed
lsruns. rm -rfis 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
.envfile full of AWS and Stripe keys, written withWrite, is blocked by DLP before it exists.
Every decision is an audit row with
surface=agent_hook. - An allowed
-
Phase 9, Governed Shell: with
builtin_exec_routing: governed_shell, the SDK does not execute an allowed built-inBashat all.- The hook rewrites the command to
mitrity-hook exec <ticket>. - The gateway runs the judged bytes in its own sandbox.
- The governed shell's proxy refuses egress to any host outside the agent's destination allowlist.
The audit event carries
execution_result.sandbox_modeand the egress counters. Phase 9 needs a gateway that serves the governed shell, which the demo image does not: see the phase 9 requirements. - The hook rewrites the command to
What to look for
While the demo runs, open your MITRITY dashboard and look at:
- Audit Log: every tool call with its governance decision, latency and policy match. Phase 8 and 9 rows carry
surface=agent_hook, and, when the gateway serves the governed shell, 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. The card shows what the runtime attested at startup: which built-in tools are hooked, which are not, and which other MCP servers are configured. The admission decisions the gateway actually saw corroborate it.
- Approval Requests: the held production deployment from Phase 5, waiting for your approval.
When the session starts, the terminal prints the attestation. At the end, it prints a summary with the counts of allowed, blocked and held actions. The summary also has the admission hook's own tally of built-in calls:
- admitted;
- allowed;
- denied;
- held;
- blocked because the edge could not be reached;
- 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 that the hook calls. The gateway connects to your MITRITY control plane over HTTPS for policy evaluation, event reporting and heartbeat. It also attests the runtime's posture, so the agent's coverage badge reflects what is actually governed. The attestation covers which built-in tools are hooked, which are not, other MCP servers and the permission mode.
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;
- 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 reports a version older than v0.21 |
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 the directory asdemo, with 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 both name the rule that matched. - Phase 9's
Bashcalls are refused withrouting_unavailable: the policy routesBashinto the governed shell, and the gateway'sexectool source is off, as it is in the demo image. See the phase 9 requirements. Withoutbuiltin_exec_routing: governed_shellon the policy, the SDK runs an admittedBash, as in phase 8. - Phase 9 is skipped: the gateway in the image reports a version older than v0.21.
Customization
- Policies: change policies in the MITRITY dashboard and re-run the demo to see different behavior.
- Workspace: mount your own files through
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 the files in
scenario/phases/to add your own test prompts.
Next steps
- Demo: Mitrity MCP Sidecar: run the same scenario with the lightweight sidecar wrapper.
- Demo: Multi-Agent Governance: a three-container stack with real delegation chains, per-agent threat intel and privilege boundaries.
- Governance coverage: what the hook covers in each framework, and how the coverage badge is computed.
- Governed shell: the sandboxed execution path behind phase 9.
- Integration modes: when to use Gateway and when to use Sidecar.
- Writing policies: create your own governance rules.
- Configuration reference: the full gateway YAML reference.