MCP Proof Encoding Specification
Version: 0.1.0 Status: Draft
This document specifies how cryptographic identity and delegation proofs are encoded and transmitted within MCP (Model Context Protocol) tool calls. The mechanism works across all MCP transports - HTTP, stdio, and WebSocket.
1. McpProof Structure
An MCP proof is a self-contained object that carries everything needed to verify an agent's identity and authority:
{
"invocation": <Invocation>,
"invoker_public_key": "<hex-encoded-ed25519-public-key>"
}| Field | Type | Description |
|---|---|---|
invocation | Invocation | The signed invocation containing the action, args, delegation chain, and proof. See Delegation Specification. |
invoker_public_key | String | The invoker's 32-byte Ed25519 public key, encoded as 64 lowercase hex characters. |
1.1 Why Hex Encoding
The invoker_public_key is hex-encoded (not base64, not a byte array) for cross-language determinism. All three target languages (Rust, TypeScript, Python) produce identical hex strings for the same bytes. JSON byte arrays and base64 variants have inconsistent behavior across serializers.
1.2 Self-Contained Verification
The McpProof is fully self-contained. The MCP server needs only the root authority's public identity to verify the proof. Specifically:
- The invoker's identity is reconstructed from
invoker_public_key. - Each delegation issuer's identity is reconstructed from the
issuer_public_keyembedded in each delegation. - The root authority's identity is provided by the server's configuration.
No external key resolver, database lookup, or network call is required.
2. Proof Placement
2.1 Injection
The proof is placed in a _proof field on the MCP tool arguments object:
{
"source": "crm",
"external_id": "123",
"_proof": {
"invocation": { ... },
"invoker_public_key": "d75a980182b10ab7..."
}
}The _proof field is a reserved protocol field. It MUST NOT be used for application data.
2.2 Extraction
The MCP server extracts the proof before passing arguments to the tool handler:
- Check if
args["_proof"]exists and is a valid McpProof object. - If present and valid, extract the proof.
- If present but invalid (wrong shape, malformed), the proof is
Nonebut the field is still stripped. - Remove
_prooffrom the arguments. - Return
(proof: Option<McpProof>, clean_args: Value).
The _proof field is always stripped from arguments, regardless of whether it contained a valid proof. Tool handlers MUST NOT receive the _proof field.
2.3 Non-Object Arguments
If the tool arguments are not a JSON object (e.g., array, string, null), proof extraction returns None and the arguments are returned unchanged. Injection into non-object arguments is a no-op.
3. Verification Algorithm
Given an McpProof and a root_identity (the server operator's AgentIdentity):
Step 1: Reconstruct Invoker Identity
- Hex-decode
proof.invoker_public_keyto 32 bytes. If not valid hex, returnDelegationChainBroken("invalid hex in invoker_public_key"). - Construct
invoker_identity = AgentIdentity::from_bytes(decoded_bytes). If not a valid 32-byte Ed25519 key, returnDelegationChainBroken("invalid Ed25519 public key in proof").
Step 2: Verify DID Consistency
Verify invoker_identity.did == proof.invocation.invoker_did. If mismatch, return DelegationChainBroken("embedded public key DID mismatch").
This prevents an attacker from substituting a different public key while keeping the original invoker DID. The DID is derived from the public key hash - a different key produces a different DID.
Step 3: Verify Invocation Chain
Call verify_invocation_with_revocation(proof.invocation, invoker_identity, root_identity, is_revoked).
This executes the full 8-step chain verification algorithm from the Delegation Specification:
- Verifies the invocation signature
- Checks invoker-delegation binding
- Walks the delegation chain, verifying every signature
- Checks every embedded public key against its claimed DID
- Checks revocation status at each level
- Verifies the chain terminates at the expected root
- Enforces all accumulated caveats
Step 4: Return Result
Return VerificationResult { invoker_did, root_did, chain, depth }.
4. Auth Modes
MCP servers can operate in three auth modes:
| Mode | Behavior |
|---|---|
Required | Reject tool calls without a valid _proof. If proof is absent, return DelegationChainBroken("no _proof provided"). If proof is present but invalid, return the verification error. |
Optional | Verify proof if present. If absent, allow the call (unauthenticated). If present but invalid, reject. |
Disabled | Skip all verification. Strip _proof from arguments but do not verify. |
4.1 Mode Configuration
Auth mode is typically set via environment variable or server configuration:
| String Value | Mode |
|---|---|
required, enforce, strict | Required |
disabled, off, none | Disabled |
Any other value (including optional) | Optional |
4.2 All-in-One Verification
The recommended entry point for MCP server middleware is verify_mcp_tool_call:
verify_mcp_tool_call(
tool_name: &str,
args: &Value,
root_identity: &AgentIdentity,
mode: McpAuthMode,
) -> Result<McpAuthOutcome, CryptoError>This function combines extraction, verification, and argument cleaning:
- Extract proof from
args(always strips_proof). - Apply mode logic:
Disabled: returnMcpAuthOutcome { verified: None, args: clean_args }.Optionalwithout proof: returnMcpAuthOutcome { verified: None, args: clean_args }.Optionalwith proof: verify and return result.Requiredwithout proof: return error.Requiredwith proof: verify and return result.
- Return
McpAuthOutcome { verified: Option<VerificationResult>, args: Value }.
4.3 Revocation Support
For revocation checking, use verify_mcp_tool_call_with_revocation which accepts an additional is_revoked: fn(&str) -> bool callback. See Delegation Specification - Revocation.
5. Creating Proofs (Agent Side)
An agent creates a proof for an MCP tool call as follows:
Obtain a delegation chain from the root authority (or from an intermediate agent).
Create the proof:
McpProof::create( invoker_keypair: &AgentKeyPair, action: &str, // the tool name or action args: Value, // the tool arguments delegation: Delegation, // the delegation chain ) -> Result<McpProof, CryptoError>This internally creates an
Invocationand extracts the invoker's public key as hex.Inject the proof into tool arguments:
proof.inject(&mut args) // adds _proof field to the args objectSend the modified
argsto the MCP server.
5.1 Constraint
The invoker MUST be the delegate_did of the provided delegation. McpProof::create returns DelegationChainBroken if this constraint is violated.
6. Wire Format Example
Complete example of an MCP tool call with proof:
{
"source": "crm",
"external_id": "customer-123",
"_proof": {
"invocation": {
"invoker_did": "did:agent:aabbccdd11223344aabbccdd11223344",
"action": "resolve",
"args": {
"source": "crm",
"external_id": "customer-123"
},
"delegation": {
"issuer_did": "did:agent:00112233445566778899aabbccddeeff",
"delegate_did": "did:agent:aabbccdd11223344aabbccdd11223344",
"issuer_public_key": [215, 90, 152, ...],
"caveats": [
{"type": "action_scope", "value": ["resolve", "search"]},
{"type": "expires_at", "value": "2026-12-31T23:59:59.999Z"}
],
"parent_proof": null,
"proof": {
"payload": { ... },
"signer_did": "did:agent:00112233445566778899aabbccddeeff",
"nonce": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"timestamp": "2026-03-15T10:00:00.000Z",
"signature": "3b6a27bcceb6a42d62a3a8d02a6f0d73..."
}
},
"proof": {
"payload": { ... },
"signer_did": "did:agent:aabbccdd11223344aabbccdd11223344",
"nonce": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"timestamp": "2026-03-15T10:05:00.000Z",
"signature": "e5564300c360ac729086e2cc806e828a..."
}
},
"invoker_public_key": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
}
}After extraction, the tool handler receives:
{
"source": "crm",
"external_id": "customer-123"
}7. Error Cases
| Error | Cause |
|---|---|
DelegationChainBroken("invalid hex in invoker_public_key") | invoker_public_key is not valid hex |
DelegationChainBroken("invalid Ed25519 public key in proof") | Decoded bytes are not a valid Ed25519 public key |
DelegationChainBroken("embedded public key DID mismatch") | Public key hash does not match claimed DID |
DelegationChainBroken("no _proof provided") | Auth mode is Required and no proof present |
SignatureInvalid | Any signature in the chain fails Ed25519 verification |
CaveatViolation(reason) | Any accumulated caveat is not satisfied |
DelegationRevoked(hash) | A delegation in the chain has been revoked |
All errors are terminal. There is no partial success or degraded verification.
