Runnable demos for cMCP and TRACE. Five demos, ~6 minutes total.
pip install cmcp-runtime
cmcp-runtime includes all dependencies (starlette, uvicorn, cmcp-verify). All demos use CMCP_DEV_MODE=1 (software-only TEE, no hardware required). The local MCP server performs real filesystem operations on ./workspace/.
python demo.py # run all three demos, pausing before each (good for live talks)
python demo.py --no-pause # run straight through
python demo.py 2 # run only demo 2
demo.py sets the token and dev mode for you and prints the detected cmcp-runtime version. If your active Python cannot find the cmcp command, it will use a local .venv if one exists (python -m venv .venv then install cmcp-runtime into it). To run the demos individually instead, use the per-demo commands below.
Set a bearer token (the cMCP Runtime requires one):
# bash
export CMCP_BEARER_TOKEN=demo-token# PowerShell
$env:CMCP_BEARER_TOKEN = "demo-token"Each demo has a run.py (works on any OS) and a run.sh (bash only). Server and cMCP Runtime logs are written to *.log files in the demo directory, not the terminal.
The agent calls three tools through the cMCP gateway. Cedar policy is enforced for every call. At session close, the gateway produces a signed TRACE claim carrying the policy bundle hash.
On real Intel TDX hardware, the policy bundle hash flows into RTMR[2] at startup. Here it appears in trace.policy.bundle_hash in the claim.
python demo-01-cmcp-in-action/run.py
What you see:
- cMCP starts, measures the policy bundle hash
write_file: Cedar allows it, real file written toworkspace/hello.txtread_file: Cedar allows it, reads the file backlist_dir: denied by Cedar policy (HTTP 403, error_code: POLICY_DENY)- TRACE claim shows
runtime.platform,runtime.measurement,policy.bundle_hash - Claim saved to
workspace/trace-claim.json(needed by demos 2 and 3)
The operator loads a different Cedar policy bundle (v1 vs v2). The claim's policy.bundle_hash changes. A verifier that pinned the v1 hash rejects v2 claims with POLICY_HASH_MISMATCH.
On real TDX hardware, the policy hash flows into RTMR[2] at startup -- the TEE measurement itself changes, not just a field in the claim.
Requires demo 1 to have run first (reads workspace/trace-claim.json).
python demo-02-policy-swap/run.py
What you see:
- v1 and v2 bundle hashes printed (visibly different SHA-256 values)
write_fileis DENIED under v2 policy (new Cedar forbid rule)cmcp verifyon the v2 claim with the pinned v1 hash ->policy_bundle.hashFAILS (POLICY_HASH_MISMATCH)cmcp verifyon the v2 claim with the v2 hash ->policy_bundle.hashPASSES (overall result stayspartially_verifiedin software-only mode, because onlyhardware_attestationis left unchecked)
The signed TRACE claim from demo-01 is verified with no gateway, no server, and no network call. Only cmcp_verify and the hashes embedded in the claim are used.
Requires demo 1 to have run first.
python demo-03-offline-trace/run.py
What you see:
- Claim fields printed:
runtime.platform,runtime.measurement,policy.bundle_hash - All cryptographic checks pass (schema, signature, policy hash, catalog hash, audit chain)
hardware_attestationis inunverified_fields, so the status readspartially_verified(software-only mode -- on real TDX that field verifies too and the status becomesverified)- No connection made to any server
Demos 1-3 are the trust chain: enforce, tamper, verify. Demo 4 is a second axis. The same tool is allowed in one workflow and denied in another, so authorization tracks the declared call context, not the tool's identity and not the model's stated intent.
python demo-04-context-enforcement/run.py
What you see:
write_fileunderworkflow_id="invoice-run": Cedar allows it (theWriteFilepermit is guarded bycontext.workflow_id == "invoice-run")- the identical
write_file, same arguments, underworkflow_id="chat-freeform": denied by Cedar (HTTP 403, POLICY_DENY) -- no permit matches, default-deny holds read_fileunderworkflow_id="chat-freeform": allowed (reads are permitted in any workflow, so the second workflow is not blocked wholesale; only the write capability is scoped)- both the allow and the deny are committed to the signed audit chain under one
policy.bundle_hash
Demo 1 denies by tool name, demo 4 by call context. Demo 5 denies by the tool's compliance attributes. A tool that is not BAA-covered is refused by a single guardrail rule, whatever it is named.
python demo-05-compliance-domain/run.py
What you see:
write_fileandread_file(compliance_domain=clinical,baa_covered=true): allowedlist_dir(compliance_domain=external-analytics,baa_covered=false): denied by Cedar (HTTP 403, POLICY_DENY)- the deny comes from
forbid ... when { context.baa_covered == false }, which overrides the baseline permit; the call is refused on its attribute, not its name - one rule covers every non-BAA-covered tool in the catalog, present and future
demos/
+-- README.md
+-- requirements.txt
+-- server/
| +-- server.py # Plain HTTP JSON-RPC 2.0 server: write_file, read_file, list_dir
| +-- requirements.txt
+-- workspace/ # Files written by demos (created at runtime)
+-- demo-01-cmcp-in-action/
| +-- cmcp-config.yaml
| +-- catalog.json # Approves write_file, read_file, list_dir
| +-- policies/ # Cedar: permit write_file+read_file, forbid list_dir
| +-- call.py # Demo agent: calls tools, closes session, saves claim
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-02-policy-swap/
| +-- catalog.json
| +-- cmcp-config.yaml # Points to policies-v1/
| +-- cmcp-config-v2.yaml # Points to policies-v2/
| +-- policies-v1/ # Cedar: permit all
| +-- policies-v2/ # Cedar: deny write_file (hash differs from v1)
| +-- check_hash.py # Computes and compares bundle hashes
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-03-offline-trace/
| +-- verify.py # cmcp_verify.verify_trace_claim (no network)
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-04-context-enforcement/
| +-- cmcp-config.yaml
| +-- catalog.json # Approves write_file, read_file (compliance_domain: finance)
| +-- policies/ # Cedar: WriteFile permitted only when context.workflow_id == "invoice-run"
| +-- call.py # Same write in two workflows: one allowed, one denied
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-05-compliance-domain/
+-- cmcp-config.yaml
+-- catalog.json # Tags tools with compliance_domain + BAA status
+-- policies/ # Cedar: forbid any tool when context.baa_covered == false
+-- call.py # Two BAA-covered tools allowed, one non-covered tool denied
+-- run.py # Cross-platform launcher (use this)
+-- run.sh # bash-only launcher
Agent (call.py)
|
v POST /mcp Authorization: Bearer <token>
[cMCP runtime :8443] <-- Cedar policy bundle (hash -> RTMR[2] on TDX)
|
v POST http://localhost:9001/mcp (JSON-RPC 2.0)
[server/server.py :9001] --> workspace/ (real file writes)
|
POST /sessions/{id}/close
|
v
TRACE claim (Ed25519 signed) --> workspace/trace-claim.json
|
v
cmcp_verify.verify_trace_claim() (no network, no operator trust)