Runnable demos for cMCP and TRACE. Three demos, ~4 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/
+-- 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
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)