Integration with cMCP¶
Understand how TRACE trust records are generated by Confidential MCP (cMCP) and how a downstream verifier checks them.
What you'll learn¶
- When and how cMCP generates a TRACE trust record
- How the TEE-sealed Ed25519 key ties the record to the hardware measurement
- What the CRYPTO-001 nonce binding is and why it matters
- Where to find the TRACE record written by cMCP
- How to pass the record to
cmcp-verifyfor full policy and audit chain verification - The division between
agentrust-trace(standalone TrustRecord signing/verification) andcmcp-verify(RuntimeClaim full chain verification)
Prerequisites¶
How cMCP Issues a TRACE Record¶
cMCP runs inside a TEE (Intel TDX, AMD SEV-SNP, or NVIDIA H100). At startup, it generates an Ed25519 signing key inside the enclave. The private key never leaves the measured TEE. This is different from a software-only key, which any process with access to the filesystem could read.
At the end of each MCP session, cMCP:
- Collects the session evidence: model identity, tool transcript, data classes, policy state
- Constructs a TRACE Trust Record dict with all required fields
- Signs the record internally using the TEE-sealed key
- Writes the signed
RuntimeClaimto the path inCMCP_TRACE_OUTPUT_PATH
The resulting file is a RuntimeClaim — a cMCP-specific envelope that wraps a TRACE GatewayTrace under the trace key, alongside gateway metadata and a top-level signature. A RuntimeClaim is not the same shape as a standalone TrustRecord; field access goes through record["trace"]["subject"], record["trace"]["policy"], and so on.
The TEE-Sealed Signing Key¶
The key used to sign cMCP TRACE records is generated inside the TEE at startup. Its public half appears in every record as cnf.jwk.
{
"cmcp_version": "0.1",
"trace": {
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "<base64url public key bytes>"
}
},
"runtime": {
"platform": "amd-sev-snp",
"measurement": "sha384:<SNP MEASUREMENT field>"
}
},
"signature": "<Ed25519 signature over the trace object>"
}
The trace.cnf.jwk and trace.runtime.measurement together make a claim: "the key that signed this record was generated by the code identified by this measurement, running inside the stated TEE." A verifier that trusts the measurement trusts the key, and by extension trusts the record.
Verifying just the Ed25519 signature (with verify_record()) confirms the record was not modified after signing. Verifying the full chain (with cmcp-verify) additionally confirms the signing key came from the stated TEE measurement.
The CRYPTO-001 Nonce Binding¶
cMCP implements the CRYPTO-001 binding defined in the TRACE spec. When the TEE generates its signing key, it also generates a TEE nonce. The first 32 bytes of the TEE nonce equal the RFC 7638 JWK Thumbprint of the signing key.
This binding ties the nonce to the key: a verifier that receives the TEE attestation report (which includes the nonce) can confirm the nonce was derived from the same key that signed the TRACE record. Substituting the key in cnf.jwk would require also forging the nonce in the hardware attestation report, which is not possible without compromising the TEE silicon.
The agentrust-trace library does not implement CRYPTO-001 nonce verification. That check is part of cmcp-verify.
Where to Find the TRACE Record¶
cMCP writes the signed TRACE record to the path set in the CMCP_TRACE_OUTPUT_PATH environment variable. In a typical deployment:
After the session closes, the file at that path contains the signed TRACE record.
import json
with open("/var/run/cmcp/session.trace.json") as f:
record = json.load(f)
print(record["trace"]["subject"]) # SPIFFE URI for the agent
print(record["trace"]["policy"]) # {"bundle_hash": "sha256:...", "enforcement_mode": "enforce"}
print(record["trace"]["cnf"]["jwk"]) # public key for verification
If CMCP_TRACE_OUTPUT_PATH is not set, cMCP emits the record to stdout as newline-delimited JSON.
Verify the Record Structure¶
The agentrust-trace library's validate_json() and verify_record() functions operate on standalone TrustRecord objects — the flat schema produced by sign_record(). A cMCP RuntimeClaim has a different envelope (TRACE fields nested under trace, plus top-level signature, gateway, and cmcp_version) and will fail validate_json() with schema errors if passed directly.
To verify a cMCP-issued RuntimeClaim, use cmcp-verify. It validates the RuntimeClaim envelope directly and handles the full chain.
Full Chain Verification with cmcp-verify¶
cmcp-verify is a separate package that performs the full TRACE verification chain for cMCP-issued records:
- Schema validation (calls
validate_json()fromagentrust-trace) - Ed25519 signature check (calls
verify_record()fromagentrust-trace) - TEE attestation report fetch and verification (platform-specific)
- CRYPTO-001 nonce binding check
- Policy audit chain verification against the
policy.bundle_hash - Appraisal status evaluation
import json
from cmcp_verify import verify_trace_claim, ApprovedHashes
with open("/var/run/cmcp/session.trace.json") as f:
claim_json = json.load(f)
# ApprovedHashes carries the expected hashes pinned at deployment time.
# Compute policy_bundle_hash from your Cedar policy archive.
# Compute tool_catalog_hash as sha256(json.dumps(catalog, sort_keys=True, separators=(',', ':')))
# on the sorted-by-tool-name catalog — canonical JSON, not raw file bytes.
approved = ApprovedHashes(
policy_bundle_hash="sha256:<expected-cedar-policy-hash>",
tool_catalog_hash="sha256:<expected-tool-catalog-hash>",
)
result = verify_trace_claim(claim_json, approved)
print(result.status.value) # "verified" | "partially_verified" | "unverified"
verify_trace_claim() fetches the TEE attestation report from the RIM URI in trace.runtime.rim_uri, so it requires network access to the attestation service for the stated platform. The returned VerificationResult exposes .verified_fields, .unverified_fields, and .failure_reason for detailed inspection.
Division of Responsibility¶
| Concern | agentrust-trace | cmcp-verify |
|---|---|---|
| Sign a standalone TrustRecord | sign_record() | N/A |
| Verify a standalone TrustRecord (Ed25519 + schema) | verify_record(), validate_json() | N/A |
| Verify a cMCP RuntimeClaim (full chain) | Not in scope | verify_trace_claim() |
| TEE measurement verification | Not in scope | Handled |
| CRYPTO-001 nonce binding | Not in scope | Handled |
| Policy audit chain | Not in scope | Handled |
| SCITT transparency receipt | Not in scope | Planned |
Use agentrust-trace directly when signing or verifying standalone TrustRecord objects. Use cmcp-verify when working with cMCP-issued RuntimeClaim files — the envelope schema is different and agentrust-trace functions cannot process it directly.
Summary¶
cMCP generates a RuntimeClaim at the end of each session, signed with an Ed25519 key that was generated inside the TEE and never exported. TRACE fields are nested under trace in the envelope; top-level field access (e.g. record["subject"]) will fail — use record["trace"]["subject"] instead. The CRYPTO-001 nonce binding ties the key to the TEE attestation report. The signed claim is written to CMCP_TRACE_OUTPUT_PATH. Use cmcp-verify for all verification of cMCP-issued records — the RuntimeClaim envelope is incompatible with the standalone validate_json() and verify_record() helpers in agentrust-trace.
Related tutorials: