Attested Response Envelope v1

Specification version 1.0 · 2026-04-21 · maintained by Medigami · open for adoption

1. Status

This specification is open. Any MCP server MAY implement it without license. Compatible implementations MAY display the Grounding Floor compatibility badge once the verification harness (§7) is published.

2. Envelope shape

{
  "payload":      <arbitrary JSON — the tool's original result>,
  "timestamp":   "2026-04-21T00:00:00Z",
  "exp":         "2026-07-20T00:00:00Z",
  "nonce":       "<16 bytes hex, unique per envelope>",
  "tracking_id": "<short opaque token, optional>",
  "algorithm":   "ed25519",
  "kid":         "<key id — stable per signing key>",
  "public_key_url":         "https://issuer.example/.well-known/mcp-pubkey.pem",
  "public_key_fingerprint": "sha256:<hex SHA-256 of the PEM bytes>",
  "signature":   "<base64(ed25519_sign(canonical_json(signed_fields)))>"
}

3. Canonical JSON (what is signed)

Before signing, the envelope is canonicalized:

The resulting byte string is signed with Ed25519. The base64 signature is then attached to the envelope.

Reference Python canonicalization

import json

EXCLUDED = {"signature", "public_key_url", "public_key_fingerprint"}

def canonicalize(env: dict) -> bytes:
    filtered = {k: v for k, v in env.items() if k not in EXCLUDED}
    return json.dumps(filtered, sort_keys=True, separators=(",", ":")).encode("utf-8")

Reference JavaScript canonicalization

const EXCLUDED = new Set([
  "signature", "public_key_url", "public_key_fingerprint",
]);

function canonicalize(env) {
  function sort(x) {
    if (Array.isArray(x)) return x.map(sort);
    if (x && typeof x === "object") {
      const out = {};
      Object.keys(x).sort().forEach((k) => {
        if (EXCLUDED.has(k)) return;
        out[k] = sort(x[k]);
      });
      return out;
    }
    return x;
  }
  return JSON.stringify(sort(env));
}

4. Fields

FieldRequiredDescription
payloadyesThe tool's original JSON result. No restriction on shape; payload may include its own tracking_id.
timestampyesISO-8601 UTC time the envelope was signed.
expyesISO-8601 UTC expiry. Verifiers MUST reject after this time.
nonceyesAt least 8 bytes of cryptographic randomness, hex-encoded.
tracking_idnoShort opaque token. If present, the issuer MAY accept outcome reports bound to this id.
algorithmyesMUST be "ed25519" for v1 of this spec.
kidyesStable per-key identifier. Used by verifiers to look up the correct public key in the issuer's ring.
public_key_urlyesHTTPS URL where the PEM-encoded public key is published. Excluded from the signed bytes.
public_key_fingerprintyesSHA-256 of the PEM bytes, hex or sha256:<hex>. Excluded from the signed bytes.
signatureyesBase64 Ed25519 signature over the canonicalized signed fields.

5. Verification procedure

  1. Parse the envelope as JSON.
  2. Check algorithm == "ed25519". Reject otherwise.
  3. Check exp is in the future. Reject otherwise.
  4. Obtain the expected public key. Either (a) use a PEM you pinned out-of-band, or (b) fetch public_key_url over HTTPS from a host in your allowlist, then check its SHA-256 matches public_key_fingerprint.
  5. Recompute the canonical bytes per §3.
  6. Verify signature against those bytes using the public key. Reject if invalid.
  7. Return {valid: true, tracking_id, timestamp, exp}.

Non-repudiation rule

An implementation MUST NOT silently fall back to its own local signing key for verification. The verifier and the signer MUST be separated in trust: the verifier accepts only caller-supplied or well-known-fetched keys. Self-vouching breaks the evidentiary property of the envelope.

6. Key rotation

Issuers publish a key ring at <origin>/.well-known/mcp-keyring.json listing current + recent {kid, pem, fingerprint, valid_from, valid_until} entries. Rotations produce a new kid; old signatures remain verifiable while the old key is listed. The recommended rotation cadence is 180 days with a 90-day grace window on the retiring key.

7. Compatibility criteria (Grounding Floor, forthcoming)

An MCP server will be considered compatible with this spec once it satisfies a public harness that exercises envelope signing, verification, expiry, and the non-repudiation rule. The harness will be published alongside v1.1 of this spec. Until then, implementations SHOULD self-test against the reference canonicalization in §3 and the verification steps in §5.

8. Notes

9. Versioning

This is version 1.0. Breaking changes produce a new spec URL (/specs/attested-response-v2), not a mutation of this one. Non-breaking clarifications update this page in-place and are noted with a dated changelog below.

Changelog