Skip to content

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:

json
{
  "invocation": <Invocation>,
  "invoker_public_key": "<hex-encoded-ed25519-public-key>"
}
FieldTypeDescription
invocationInvocationThe signed invocation containing the action, args, delegation chain, and proof. See Delegation Specification.
invoker_public_keyStringThe 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_key embedded 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:

json
{
  "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:

  1. Check if args["_proof"] exists and is a valid McpProof object.
  2. If present and valid, extract the proof.
  3. If present but invalid (wrong shape, malformed), the proof is None but the field is still stripped.
  4. Remove _proof from the arguments.
  5. 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

  1. Hex-decode proof.invoker_public_key to 32 bytes. If not valid hex, return DelegationChainBroken("invalid hex in invoker_public_key").
  2. Construct invoker_identity = AgentIdentity::from_bytes(decoded_bytes). If not a valid 32-byte Ed25519 key, return DelegationChainBroken("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:

ModeBehavior
RequiredReject 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.
OptionalVerify proof if present. If absent, allow the call (unauthenticated). If present but invalid, reject.
DisabledSkip 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 ValueMode
required, enforce, strictRequired
disabled, off, noneDisabled
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:

  1. Extract proof from args (always strips _proof).
  2. Apply mode logic:
    • Disabled: return McpAuthOutcome { verified: None, args: clean_args }.
    • Optional without proof: return McpAuthOutcome { verified: None, args: clean_args }.
    • Optional with proof: verify and return result.
    • Required without proof: return error.
    • Required with proof: verify and return result.
  3. 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:

  1. Obtain a delegation chain from the root authority (or from an intermediate agent).

  2. 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 Invocation and extracts the invoker's public key as hex.

  3. Inject the proof into tool arguments:

    proof.inject(&mut args)  // adds _proof field to the args object
  4. Send the modified args to 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:

json
{
  "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:

json
{
  "source": "crm",
  "external_id": "customer-123"
}

7. Error Cases

ErrorCause
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
SignatureInvalidAny 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.

The identity and delegation layer for AI agents.