Attested Response Envelope v1
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:
- Object keys sorted lexicographically (UTF-8 byte order).
- No insignificant whitespace:
JSON.stringifywith no space / indent argument. - Three fields are excluded from the signed
bytes because they are added after signing:
signature,public_key_url,public_key_fingerprint.
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
| Field | Required | Description |
|---|---|---|
payload | yes | The tool's original JSON result. No restriction on shape; payload may include its own tracking_id. |
timestamp | yes | ISO-8601 UTC time the envelope was signed. |
exp | yes | ISO-8601 UTC expiry. Verifiers MUST reject after this time. |
nonce | yes | At least 8 bytes of cryptographic randomness, hex-encoded. |
tracking_id | no | Short opaque token. If present, the issuer MAY accept outcome reports bound to this id. |
algorithm | yes | MUST be "ed25519" for v1 of this spec. |
kid | yes | Stable per-key identifier. Used by verifiers to look up the correct public key in the issuer's ring. |
public_key_url | yes | HTTPS URL where the PEM-encoded public key is published. Excluded from the signed bytes. |
public_key_fingerprint | yes | SHA-256 of the PEM bytes, hex or sha256:<hex>. Excluded from the signed bytes. |
signature | yes | Base64 Ed25519 signature over the canonicalized signed fields. |
5. Verification procedure
- Parse the envelope as JSON.
- Check
algorithm == "ed25519". Reject otherwise. - Check
expis in the future. Reject otherwise. - Obtain the expected public key. Either (a) use a PEM you pinned
out-of-band, or (b) fetch
public_key_urlover HTTPS from a host in your allowlist, then check its SHA-256 matchespublic_key_fingerprint. - Recompute the canonical bytes per §3.
- Verify
signatureagainst those bytes using the public key. Reject if invalid. - 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
- This spec documents provenance. Admissibility in any legal or regulatory proceeding is determined by the applicable trier of fact; signature verification is a factual input, not a legal conclusion.
- The
payloadfield may carry its own application- specific disclaimers, including state-specific UPL-safe disclaimers. These are outside the scope of this spec but are preserved verbatim under the signature. - This spec does not define transport. It is usable over MCP SSE, MCP stdio, HTTP, or any other channel that can carry JSON.
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
- 2026-04-21 — v1.0 published.