From fa07223662a505238e31124e6784e1077441c4f6 Mon Sep 17 00:00:00 2001 From: vaaraio Date: Sun, 31 May 2026 14:01:11 +0300 Subject: [PATCH 1/9] SEP: Server-Side Signed Execution Record for MCP Tool Calls Co-Authored-By: Claude Opus 4.8 --- seps/server-side-signed-execution-record.md | 597 ++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 seps/server-side-signed-execution-record.md diff --git a/seps/server-side-signed-execution-record.md b/seps/server-side-signed-execution-record.md new file mode 100644 index 000000000..67dc528fe --- /dev/null +++ b/seps/server-side-signed-execution-record.md @@ -0,0 +1,597 @@ +# SEP-XXXX: Server-Side Signed Execution Record for MCP Tool Calls + + +- **Status**: Draft +- **Type**: Standards Track (Extensions Track) +- **Created**: 2026-05-31 +- **Author(s)**: vaaraio (@vaaraio) +- **Sponsor**: None (seeking sponsor) +- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/XXXX +- **Requires**: SEP-2787 (Tool call attestation) +- **Related**: SEP-2817 (AI Invocation Audit Context in Request `_meta`), SEP-414 (request `_meta`) +- **Replaces**: None +- **Discussions-To**: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2704 + +## Abstract + +This SEP defines two server-authoritative, cryptographically signed records +for MCP tool calls: a **decision record** emitted by the governing server or +proxy before the side effect runs, and an **outcome record** emitted after +execution completes. The decision record binds what was requested, the policy +decision (allow, block, or escalate), and the risk basis for that decision. The +outcome record binds the execution status (`executed`, `refused`, or `errored`), +a commitment over the result, and timing. The two are paired through a +`backLink` and bound to the originating request, so a verifier can reconstruct +what the agent was permitted to do, why, and what it actually did. + +These records are signed by the server or proxy that governs execution +(`issuerAsserted` / `receiptAsserted`), which is a different trust surface from +the client-asserted attestation of the call in SEP-2787 and the client-asserted +input audit context in SEP-2817. The records use RFC 8785 (JCS) canonical JSON, +a detached signature that does not cover itself, and are offline-verifiable by a +standard-library checker. The wire schema is the shape already shipping in the +Vaara MCP proxy (`vaara.attestation.receipt`), which also provides an +independent verifier and JCS conformance vectors. + +## Motivation + +EU AI Act Article 12 obliges providers and deployers of high-risk AI systems to +keep automatic records of events ("logs") over the system's lifetime, including +records adequate to identify situations that may cause the system to present a +risk and to support post-market monitoring under Article 72. For an +agentic MCP deployment, the regulator's question is not only "what did the +client claim it wanted to do" but "what did the governing system decide, on what +basis, and what did the tool call actually do." That is a statement the server +or governance layer must make, because it is the party that holds the decision +logic and observes the result. + +SEP-2787 standardizes a signed attestation of a tool **call**: it binds the +agent identity, tool name, intent, and an argument commitment into a verifiable +envelope. SEP-2817 standardizes optional, explicitly non-authoritative, +**client-asserted** input audit context (`invocationReason`, `model`, +`userIntent`, `turnId`). Both describe the input side: what was claimed, by the +party making the call. Neither carries the governing system's decision or the +recorded outcome. SEP-2817 says so directly: "server-side decision records, +stable tool-call identity, agent/session correlation, and taxonomies are left to +follow-up SEPs," and Discussion #2704 frames the server-authoritative record as +the deferred half. This SEP is that follow-up. + +Client-asserted records alone cannot satisfy Article 12 for a deployment where +the server enforces policy. A client can claim its intent and its arguments; it +cannot credibly attest that the call was allowed, why it was allowed or blocked, +or what the tool returned, because it does not own that logic and is not a +neutral observer of its own behavior. A regulator auditing an incident needs a +record signed by the enforcement point, paired to the request it answers, that +survives the client. Without a standard for that record, every governance vendor +invents an incompatible one, and cross-implementation verification (the point of +a conformance test) is impossible. + +The records are also the natural place to satisfy the rest of the Article 12 and +Article 14 surface that input audit does not reach: the allow/block/escalate +decision (human-oversight evidence under Article 14), the risk basis that drove +it (risk-management evidence under Article 9), and the post-execution outcome +that feeds post-market monitoring (Article 72). A shipping implementation already +emits all of these as audit events; this SEP standardizes the signed wire shape +so they are portable and independently verifiable. + +## Specification + +### Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in RFC 2119 and RFC 8174. + +- **Governing server**: the MCP server, or a proxy in front of it, that decides + whether a tool call runs and observes the result. It is the issuer of both + records defined here. +- **Decision record**: a signed envelope emitted before the side effect, + carrying the policy decision and its risk basis. +- **Outcome record**: a signed envelope emitted after execution, carrying the + execution status and a result commitment. +- **Request attestation**: the SEP-2787 envelope describing the tool call. It is + the anchor both records point back to. + +### Trust boundary + +This SEP is server-authoritative. Both records are signed under the governing +server's key and place their bound claims in an issuer block +(`issuerAsserted` for the decision record, `receiptAsserted` for the outcome +record), mirroring the SEP-2787 trust-surface split. The records make claims +about the **decision** and the **execution**, which only the enforcement point +can make. + +This is distinct from SEP-2787, where the issuer attests the **call** (identity, +tool, args) and the planner declares intent, and from SEP-2817, where the client +asserts input context that is explicitly not authorization evidence. A verifier +MUST treat the three as separate surfaces: a SEP-2787 attestation and a SEP-2817 +`_meta` block describe what was asked for; the records in this SEP describe what +the governing system did about it. A server MUST NOT copy client-asserted +SEP-2817 fields into the signed decision or outcome record in a way that implies +the server vouches for their truth; it MAY reference them by `turnId` for +correlation (see Pairing). + +### Canonicalization and signature + +Both records use RFC 8785 (JCS) canonical JSON. The signature is computed over +the JCS-canonical encoding of the record's blocks **excluding** the `signature` +field, and the `signature` field carries the result; it does not cover itself. +This is identical to the SEP-2787 and the shipped Vaara execution-receipt scheme, +so a verifier that already handles SEP-2787 signatures handles these records with +no new cryptographic code. + +- The `alg` field MUST be one of `ES256`, `RS256`, or `HS256`. Production + deployments crossing a trust boundary SHOULD use an asymmetric algorithm + (`ES256` or `RS256`) so verifiers need only the public key. +- IEEE-754 floats MUST NOT appear anywhere in a canonicalized body. Numeric + values that are not integers MUST be encoded as scaled integers or decimal + strings. This removes the most common source of cross-stack signature drift + and matches the Vaara JCS discipline. +- All digests are encoded as the string `sha256:`. +- All timestamps are RFC 3339 / ISO 8601 UTC strings with second precision and a + trailing `Z` (for example `2026-05-31T09:30:00Z`). + +### Argument and result commitments + +Both records reuse the SEP-2787 commitment shapes verbatim. A commitment is one +of: + +- **`ArgsRef`**: a content-addressed reference, `{ "ref": "", "digest": + "sha256:", "canonicalization": "jcs" }`. The verifier MAY fetch `ref` and + check the digest. +- **`ArgsProjection`**: a reviewed projection, `{ "projection": "", + "projectionDigest": "sha256:" }`, where `projection` is the JCS-canonical + encoding of the projection object as a UTF-8 string and `projectionDigest` is + the sha256 over those bytes. + +Commitment-only audit, where the payload never leaves the server, is expressed as +a hash-only-identity `ArgsProjection` whose `projection` is the JCS encoding of +`{ "digest": "sha256:" }` and whose embedded digest binds the underlying +value. This is the shipped `make_args_digest` behavior and is the RECOMMENDED +default for the outcome record's `resultCommitment` so that results, which may +contain personal data, are committed to without being copied into the record. + +### Decision record + +A decision record MUST be emitted by the governing server before the tool call's +side effect runs, for every governed call. It is a JSON object with the +following top-level fields. + +| Field | Type | Required | Description | +| ---------------- | -------- | -------- | --------------------------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1` for this SEP. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Join to the SEP-2787 attestation (see below). | +| `decisionDerived`| object | yes | The decision and its risk basis (see below). | +| `issuerAsserted` | object | yes | The governing server's issuer block (see below). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | + +**`backLink`** joins the decision to the request attestation it governs: + +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------ | +| `attestationDigest`| string | yes | `sha256:` over the JCS-canonical full SEP-2787 attestation wire bytes, signature included. Pins the exact attestation instance. | +| `attestationNonce` | string | yes | Echoes the attestation's `issuerAsserted.nonce` for fast correlation. | + +If no SEP-2787 attestation exists for the call (the deployment does not run +2787), the server MUST instead bind the request by setting `attestationDigest` to +`sha256:` over the JCS-canonical encoding of the request envelope it +observed (the `tools/call` params plus `_meta`), and set `attestationNonce` to a +server-chosen per-call nonce. The binding is to the request **instance**, not +only its content. + +**`decisionDerived`** carries the decision and the basis for it: + +| Field | Type | Required | Description | +| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------ | +| `decision` | string | yes | One of `allow`, `block`, `escalate`. | +| `reason` | string | no | Short human-readable basis for the decision. | +| `riskScore` | string | no | The risk estimate that drove the decision, as a decimal string (floats are prohibited on the wire). | +| `thresholdAllow` | string | no | The allow threshold in force at decision time, as a decimal string. | +| `thresholdBlock` | string | no | The block threshold in force at decision time, as a decimal string. | +| `policyId` | string | no | Identifier or digest of the policy/ruleset version that produced the decision. | +| `decidedAt` | string | yes | ISO 8601 UTC timestamp of the decision. | + +A `decision` of `escalate` means the call was held for human oversight; the +outcome record for that call will report `refused` if the human declined, or a +later decision record MAY supersede it (see Pairing). + +**`issuerAsserted`** is the governing server's signed block: + +| Field | Type | Required | Description | +| -------------- | ------- | -------- | --------------------------------------------------------------------- | +| `iss` | string | yes | Issuer identity (the governing server or proxy). | +| `sub` | string | yes | Subject the decision is about (tenant, agent, or upstream identity). | +| `iat` | string | yes | ISO 8601 UTC issuance time. | +| `nonce` | string | yes | Unique per record. | +| `secretVersion`| string | yes | Key/secret version identifier for rotation and dispatch. | +| `alg` | string | yes | MUST equal the top-level `alg`. | + +### Outcome record + +An outcome record MUST be emitted by the governing server after the governed call +completes (or is refused), and MUST be paired to a decision record for the same +call. It is a JSON object with the following top-level fields. This is the shape +already shipping as `vaara.attestation.receipt.ExecutionReceipt`. + +| Field | Type | Required | Description | +| ---------------- | -------- | -------- | ------------------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1`. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Same shape as the decision record's `backLink`; pins the request. | +| `outcomeDerived` | object | yes | Execution status, timing, and result commitment (see below). | +| `receiptAsserted`| object | yes | The governing server's issuer block (same shape as `issuerAsserted`, minus `expSeconds`; an outcome record is a durable record, not a capability, so it carries no `exp` and verifiers enforce no TTL). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | + +**`outcomeDerived`** carries what happened: + +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------ | +| `status` | string | yes | One of `executed`, `refused`, `errored`. | +| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | +| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | + +### Pairing + +A decision record and an outcome record describe the same governed call when +both carry the same `backLink` (`attestationDigest` and `attestationNonce` +equal). A verifier that has both, plus the SEP-2787 attestation, can confirm the +full chain: the attestation pins the call; the decision record pins the policy +verdict and risk basis for that call; the outcome record pins what the call did. +This is instance-binding, not only content-binding: two byte-identical calls +produce two attestations with distinct nonces and therefore distinct +`attestationDigest` values, so a decision or outcome record cannot be replayed +against a different instance of the same call. + +For correlation with client-asserted input context (SEP-2817), a server MAY +include the SEP-2817 `turnId` as an additional, clearly client-asserted field +inside `decisionDerived` under the key `clientTurnId`. This is correlation only; +its presence in the signed body means the server records that the client claimed +this `turnId`, not that the server vouches for it. + +A superseding decision (for example, a human resolving an `escalate`) is recorded +as a new decision record with the same `backLink` and a later `decidedAt`. The +record with the latest `decidedAt` for a given `backLink` is the effective +decision; earlier ones are retained as history. Verifiers MUST NOT treat +multiple decision records for one `backLink` as a conflict. + +### Transport + +The records are signed JSON objects and are transport-agnostic. A server MAY +return the outcome record in the `tools/call` response `_meta` under the reserved +key `io.modelcontextprotocol/executionRecord`, MAY return the decision record +under `io.modelcontextprotocol/decisionRecord`, and MAY persist either to an +out-of-band audit store. When a record is too large to carry inline (a large +`ArgsRef` chain), the server SHOULD return an `ArgsRef` pointing to the stored +record. This SEP does not require any change to existing MCP message formats; the +records ride in `_meta`, consistent with SEP-414, SEP-2787, and SEP-2817. + +### JSON examples + +Decision record (allow, with risk basis): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "decisionDerived": { + "decision": "allow", + "reason": "risk below allow threshold", + "riskScore": "0.21", + "thresholdAllow": "0.40", + "thresholdBlock": "0.70", + "policyId": "sha256:3c9d4b8a", + "decidedAt": "2026-05-31T09:30:00Z", + "clientTurnId": "turn-7f3a" + }, + "issuerAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:00Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Yb7Qd2mK9sV", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3045022100ab12" +} +``` + +Outcome record (executed, result committed by digest only): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "outcomeDerived": { + "status": "executed", + "completedAt": "2026-05-31T09:30:02Z", + "resultCommitment": { + "projection": "{\"digest\":\"sha256:1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c\"}", + "projectionDigest": "sha256:aa11bb22cc33dd44ee55ff66aa77bb88cc99dd00ee11ff22aa33bb44cc55dd66" + } + }, + "receiptAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:02Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Zc8Re3nL0tW", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3046022100cd34" +} +``` + +Outcome record (refused, no result): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "outcomeDerived": { + "status": "refused", + "completedAt": "2026-05-31T09:30:01Z" + }, + "receiptAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:01Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Wd9Sf4oM1uX", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3045022100ef56" +} +``` + +### Verification algorithm + +A verifier holding a decision record, an outcome record, the SEP-2787 +attestation, and the governing server's public key (or shared secret for HS256) +MUST perform, and a conforming implementation MUST pass, the following checks: + +1. For each record, recompute the JCS-canonical encoding of the body with the + `signature` field removed, and verify `signature` under the issuer key for the + record's `alg`. Reject on mismatch. +2. Recompute `attestationDigest` from the SEP-2787 attestation wire bytes and + confirm both records' `backLink.attestationDigest` and + `backLink.attestationNonce` match it. Reject on mismatch. +3. Confirm the two records share the same `backLink` (they describe the same + call). +4. If `resultCommitment` is present and the verifier has the result payload, + recompute the commitment digest from the runtime result and confirm it + matches. Reject on mismatch. +5. Confirm `decisionDerived.decision` is one of `allow`, `block`, `escalate` and + `outcomeDerived.status` is one of `executed`, `refused`, `errored`. + +## Rationale + +**Two records, not one.** The decision and the outcome are made at different +times by the same party and answer different regulatory questions (Article 9 and +14 risk and oversight evidence versus Article 72 post-market outcome evidence). A +single combined record would force the server to delay signing until after +execution, losing the pre-side-effect commitment that proves the verdict was +fixed before the action ran. Splitting them, paired by `backLink`, preserves the +commit-before-execute property that is the whole point of an enforcement record. + +**Instance-binding over content-binding.** Earlier drafts in the #2704 / #2817 +discussion considered binding records to the content hash of the call alone. That +lets a record be replayed against any byte-identical call. Binding to the +SEP-2787 attestation digest (signature included) pins the exact attestation +instance, so distinct calls with identical content still produce distinct +bindings. This was the resolved position in discussion; the agent-guard author +conceded the instance-binding point on 2026-05-30, and this SEP adopts it as +normative. + +**Reusing the SEP-2787 stack.** The outcome record is the post-execution sibling +of the SEP-2787 attestation and deliberately reuses its canonicalization, signing +algorithms, commitment shapes, and issuer-block layout. A verifier that handles +SEP-2787 handles these records with no new cryptographic code, and the decision +record adds only one new block (`decisionDerived`). + +**Why server-authoritative.** A client cannot credibly attest a decision it did +not make or an outcome it is not a neutral observer of. SEP-2787 and SEP-2817 +cover the input side precisely because they are client or issuer claims about the +call. The enforcement record has to come from the enforcement point, which is why +this is a separate SEP rather than an extension of either. + +**Alternatives considered.** Carrying the decision inside the SEP-2787 +`issuerAsserted` block was rejected because SEP-2787 attests the call, not the +verdict, and overloading it would blur the trust surface that SEP made explicit. +Adopting a content-addressed `action_ref` as the primary binding field was +rejected: a content-addressed receipt id is a useful secondary index but is +content-binding, and using it as the primary join reintroduces the replay +problem instance-binding solves. The `action_ref` style is reconcilable as an +optional `ArgsRef`-shaped pointer, not as the default join. + +## Backward Compatibility + +This SEP introduces no breaking changes. Both records are new, optional `_meta` +payloads under reserved keys; servers that do not emit them and clients that do +not consume them are unaffected. The schema reuses the SEP-2787 commitment and +issuer-block shapes, so an existing SEP-2787 verifier extends to these records by +adding the `decisionDerived` block and the `outcomeDerived.status` enum, with no +change to the canonicalization or signature code. The records do not alter any +existing MCP request or response method. + +## Security Implications + +**Signing-key compromise and post-hoc backdating.** A signed record proves the +holder of the key bound those values, but a compromised key lets an adversary +mint records with any `decidedAt` / `completedAt` they choose, including +backdated ones, to fabricate a clean history. A signature alone cannot defeat +this. The defense is an **external time anchor**: periodically anchoring the head +of the append-only audit chain that carries these records to an independent, +trusted timestamp (an RFC 3161 timestamp token, or an eIDAS qualified electronic +timestamp) so that records signed after a compromise cannot be inserted before +the last anchored head without detection. The Vaara reference implementation +binds each record into a hash chain (chain version 2, with tenant identity bound +into the chained hash) and ships the external anchor in v0.48: an RFC 3161 +timestamp over the chain head, verifiable offline, with optional automatic cadence +anchoring that fails open by writing a gap marker into the chain when the +authority is unreachable. Anchoring is opt-in. The deployer configures a trusted +timestamp authority; none is bundled by default. Offline verification proves the +token was signed by the certificate it carries; establishing that certificate as +a trusted (for example eIDAS-qualified) authority is deployer policy, enforced by +pinning it. A conforming deployment under EU AI Act retention obligations SHOULD +anchor the chain head externally at a cadence proportionate to its risk. + +**Replay.** Instance-binding through the SEP-2787 attestation digest (signature +included) plus the per-record `nonce` means a record cannot be replayed against a +different call instance. Verifiers MUST reject a record whose `backLink` does not +match the attestation under verification. + +**Instance versus content binding.** As above, binding is to the attestation +instance, not only the call content. Implementations MUST NOT substitute a bare +content hash of the arguments for the `attestationDigest`, because that +reintroduces replay across byte-identical calls. + +**Personal data minimization.** Tool arguments and results can contain personal +data. The RECOMMENDED default for both `decisionDerived` (which never copies the +arguments, only a risk basis and decision) and `outcomeDerived.resultCommitment` +(commitment-only hash-only-identity projection) is that the record commits to a +digest of the value without copying the value. Where a reviewed projection is +needed for audit, servers SHOULD redact to the minimum fields necessary and +commit to the projection, never the raw payload. This keeps the signed, +long-retained record free of personal data while preserving the ability to prove, +given the original value out of band, that it is the one the record committed to. +This aligns with GDPR data minimization and storage limitation alongside the +Article 12 retention obligation. + +**Issuer identity and key distribution.** Verifiers must obtain the governing +server's public key out of band; embedding a public key in the record is a +convenience for local verification only and is not trust-establishing. The +`secretVersion` field supports key rotation; verifiers dispatch on it. + +**Denial of service.** Record emission MUST NOT block legitimate traffic; a +governing server that cannot sign SHOULD fail closed on the **decision** (do not +allow an ungoverned call) but record the signing failure, and MAY degrade outcome +recording to a logged error rather than dropping the call's result. The reference +implementation emits records on a best-effort path that never blocks the proxied +call and counts emission failures for operator alerting. + +## Prior Art Reconciled + +- **SEP-2787 (Tool call attestation, author soup-oss, Extensions Track, open).** + Attests the tool **call**: planner-declared intent, issuer-asserted identity, + payload-derived tool bindings and argument commitment. This SEP is its + post-decision and post-execution counterpart and reuses its canonicalization, + signing, commitment shapes, and trust-surface layout. The `backLink` pins a + SEP-2787 attestation. The split between what was asked (2787) and what was + decided and done (this SEP) is deliberate and preserves 2787's trust surface. + +- **SEP-2817 (AI Invocation Audit Context in Request `_meta`, author hangum, + Standards Track, draft seeking sponsor).** Standardizes optional, + client-asserted input audit context (`invocationReason`, `model`, `userIntent`, + `turnId`) and states explicitly that these fields are not authorization + evidence and that server-side decision records are left to a follow-up SEP. + This is that follow-up. Correlation with 2817 is by optionally recording the + client-asserted `turnId` as `clientTurnId`, clearly marked as a client claim. + +- **agent-guard (XuebinMa, Rust).** Models a `Guard` producing an `AuditRecord` + and an `ExecutionProof`. The two-record decision/outcome split here covers the + same ground as `AuditRecord` plus `ExecutionProof`, with the binding made + explicit and instance-scoped through the SEP-2787 attestation digest rather + than left to a content hash. The instance-binding position was conceded by the + agent-guard author in discussion on 2026-05-30 and is adopted here as + normative. + +- **Content-addressed receipt identifiers.** Some designs identify a record by a + content-addressed id of the form `action_ref = sha256(JCS(...))` over a + description of the action. This SEP does not adopt a content-addressed id as the + default join field, because it is content-binding and reintroduces + cross-instance replay when used as the primary binding. Such a pointer is + reconcilable as an optional `ArgsRef`-shaped reference (the `ref` carries the id + and the `digest` pins it), not as the normative pairing key, which remains the + instance-scoped `backLink`. + +## Reference Implementation + +The wire schema in this SEP is the shape shipping in the Vaara MCP proxy +(v0.48; the receipt library landed in v0.42). Relevant modules: + +- `vaara/attestation/_receipt_types.py`: the `ExecutionReceipt` envelope + (`version`, `alg`, `backLink`, `outcomeDerived`, `receiptAsserted`, + `signature`), `OutcomeDerived` with `status` constrained to `executed` / + `refused` / `errored`, and `BackLink` (`attestationDigest`, + `attestationNonce`). This is the outcome record of this SEP, byte for byte. +- `vaara/attestation/_receipt_emit.py`: builds, JCS-canonicalizes (signing input + excludes `signature`), and signs the outcome record; verifies the signature. +- `vaara/attestation/_receipt_verifier.py`: `make_back_link` / `verify_back_link` + and `attestation_digest` (sha256 over the JCS-canonical full SEP-2787 wire + bytes, signature included): the instance-binding join. +- `vaara/attestation/decision.py`: the decision record of this SEP. + `DecisionRecord` / `DecisionDerived`, `emit_decision_record`, + `verify_decision_signature`, `verify_decision_back_link`, and + `records_paired` (the decision-and-outcome join). Reuses the receipt's + back-link, the issuer-block layout, and the shared signing stack unchanged, + so the decision record adds the `decisionDerived` block and no new crypto. +- `vaara/attestation/_sep2787_types.py` and `_sep2787_canonical.py`: the shared + commitment shapes (`ArgsRef`, `ArgsProjection`, `make_args_digest`), the + issuer-block layout, RFC 8785 canonicalization with float rejection, and the + signing stack (`ES256` / `RS256` / `HS256`) reused unchanged. +- `vaara/integrations/_mcp_attest.py` and `mcp_proxy.py`: the proxy emits the + SEP-2787 attestation before the call and the paired outcome record after it, + on a best-effort path that never blocks proxied traffic. +- `vaara/audit/trail.py`: the append-only, hash-chained audit trail the records + are written into; chain version 2 binds `tenant_id` and `chain_version` into + the chained hash so a record cannot be silently re-attributed to another + tenant. +- `vaara/audit/timeanchor.py` and `AuditTrail.enable_auto_anchor`: the external + time anchor over the chain head (Security Implications), shipped in v0.48 as an + RFC 3161 client plus an offline verifier, with optional automatic cadence + anchoring that writes an `ANCHOR_GAP` marker into the chain when the authority + is unreachable. + +**Bridge from the shipped audit decision.** The audit trail already records the +pre-execution decision as a hash-chained `CommitPayload` +(`vaara/audit/receipts.py`: `decision`, `risk_score`, `threshold_allow`, +`threshold_deny`, `decided_at`). `decision_derived_from_commit` maps that +payload onto the signed `decisionDerived` wire shape. The mapping is mechanical +but not a rename: the verdict vocabulary is normalized (the audit layer records +`deny`, the wire enum uses `block`; the review family maps to `escalate`), the +float risk basis becomes decimal strings (floats are banned on the wire), and +the epoch decision time becomes an ISO 8601 UTC string. `policy_id`, +`clientTurnId`, and `reason` are not carried on the commit payload and are +supplied by the caller when available. This keeps the long-retained signed +record free of the float canonicalization drift that the hash-chained payload +tolerated internally. + +## Test Vectors + +The Vaara conformance vectors for the SEP-2787 canonicalization and signature +(`modelcontextprotocol/modelcontextprotocol#2789`) cover the JCS encoding, float +rejection, and the detached-signature scheme that both records in this SEP reuse. +A standard-library-only checker (no Vaara import; `cryptography` for the +asymmetric case) verifies a signed export offline, mirroring the +`scripts/verify_vaara_trail.py` approach. For Standards Track finalization, this +SEP will add: + +- JCS canonical vectors for the `decisionDerived` block and the + `outcomeDerived.status` enum, in the same vector format as #2789. +- A paired decision-plus-outcome fixture with its SEP-2787 attestation, exercising + the full verification algorithm above, including a replay-rejection case + (mismatched `backLink`) and a superseding-decision case (two decision records, + same `backLink`, distinct `decidedAt`). +- A `sep-XXXX.yaml` traceability file mapping each MUST / MUST NOT and + SHOULD / SHOULD NOT in the Specification to a conformance check ID, as required + for Standards Track SEPs reaching Final. + +## Acknowledgments + +This proposal builds directly on SEP-2787 (soup-oss) and SEP-2817 (hangum), and +on the server-authoritative record direction set out in Discussion #2704. The +instance-binding versus content-binding discussion with the agent-guard author +(XuebinMa) shaped the normative pairing rule. From 68e8d17814b5c72e41ede852be4239d53a534dc9 Mon Sep 17 00:00:00 2001 From: vaaraio Date: Sun, 31 May 2026 14:01:15 +0300 Subject: [PATCH 2/9] seps: assign SEP number 2828 from PR Co-Authored-By: Claude Opus 4.8 --- ...-record.md => 2828-server-side-signed-execution-record.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename seps/{server-side-signed-execution-record.md => 2828-server-side-signed-execution-record.md} (99%) diff --git a/seps/server-side-signed-execution-record.md b/seps/2828-server-side-signed-execution-record.md similarity index 99% rename from seps/server-side-signed-execution-record.md rename to seps/2828-server-side-signed-execution-record.md index 67dc528fe..480a17087 100644 --- a/seps/server-side-signed-execution-record.md +++ b/seps/2828-server-side-signed-execution-record.md @@ -1,4 +1,4 @@ -# SEP-XXXX: Server-Side Signed Execution Record for MCP Tool Calls +# SEP-2828: Server-Side Signed Execution Record for MCP Tool Calls - **Status**: Draft @@ -6,7 +6,7 @@ - **Created**: 2026-05-31 - **Author(s)**: vaaraio (@vaaraio) - **Sponsor**: None (seeking sponsor) -- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/XXXX +- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2828 - **Requires**: SEP-2787 (Tool call attestation) - **Related**: SEP-2817 (AI Invocation Audit Context in Request `_meta`), SEP-414 (request `_meta`) - **Replaces**: None From 529f15a4514990b4f1e7e8fa0fdd7c61fa647b0f Mon Sep 17 00:00:00 2001 From: vaaraio Date: Sun, 31 May 2026 14:02:58 +0300 Subject: [PATCH 3/9] docs(seps): render SEP-2828 docs mirror (generate:seps) Co-Authored-By: Claude Opus 4.8 --- docs/docs.json | 3 +- ...28-server-side-signed-execution-record.mdx | 611 ++++++++++++++++++ docs/seps/index.mdx | 89 +-- 3 files changed, 658 insertions(+), 45 deletions(-) create mode 100644 docs/seps/2828-server-side-signed-execution-record.mdx diff --git a/docs/docs.json b/docs/docs.json index 68f2472b6..1907873dc 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -455,7 +455,8 @@ "seps/2106-json-schema-2020-12", "seps/2164-resource-not-found-error", "seps/2484-conformance-tests-required-for-final-seps", - "seps/2596-spec-feature-lifecycle-and-deprecation" + "seps/2596-spec-feature-lifecycle-and-deprecation", + "seps/2828-server-side-signed-execution-record" ] } ] diff --git a/docs/seps/2828-server-side-signed-execution-record.mdx b/docs/seps/2828-server-side-signed-execution-record.mdx new file mode 100644 index 000000000..5859d2f1f --- /dev/null +++ b/docs/seps/2828-server-side-signed-execution-record.mdx @@ -0,0 +1,611 @@ +--- +title: "SEP-2828: Server-Side Signed Execution Record for MCP Tool Calls" +sidebarTitle: "SEP-2828: Server-Side Signed Execution Record for…" +description: "Server-Side Signed Execution Record for MCP Tool Calls" +--- + +
+ + Draft + + + Standards Track (Extensions Track) + +
+ +| Field | Value | +| ------------- | ------------------------------------------------------------------------------- | +| **SEP** | 2828 | +| **Title** | Server-Side Signed Execution Record for MCP Tool Calls | +| **Status** | Draft | +| **Type** | Standards Track (Extensions Track) | +| **Created** | 2026-05-31 | +| **Author(s)** | vaaraio ([@vaaraio](https://github.com/vaaraio)) | +| **Sponsor** | None (seeking sponsor) | +| **PR** | [#2828](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2828) | + +--- + +## Abstract + +This SEP defines two server-authoritative, cryptographically signed records +for MCP tool calls: a **decision record** emitted by the governing server or +proxy before the side effect runs, and an **outcome record** emitted after +execution completes. The decision record binds what was requested, the policy +decision (allow, block, or escalate), and the risk basis for that decision. The +outcome record binds the execution status (`executed`, `refused`, or `errored`), +a commitment over the result, and timing. The two are paired through a +`backLink` and bound to the originating request, so a verifier can reconstruct +what the agent was permitted to do, why, and what it actually did. + +These records are signed by the server or proxy that governs execution +(`issuerAsserted` / `receiptAsserted`), which is a different trust surface from +the client-asserted attestation of the call in SEP-2787 and the client-asserted +input audit context in SEP-2817. The records use RFC 8785 (JCS) canonical JSON, +a detached signature that does not cover itself, and are offline-verifiable by a +standard-library checker. The wire schema is the shape already shipping in the +Vaara MCP proxy (`vaara.attestation.receipt`), which also provides an +independent verifier and JCS conformance vectors. + +## Motivation + +EU AI Act Article 12 obliges providers and deployers of high-risk AI systems to +keep automatic records of events ("logs") over the system's lifetime, including +records adequate to identify situations that may cause the system to present a +risk and to support post-market monitoring under Article 72. For an +agentic MCP deployment, the regulator's question is not only "what did the +client claim it wanted to do" but "what did the governing system decide, on what +basis, and what did the tool call actually do." That is a statement the server +or governance layer must make, because it is the party that holds the decision +logic and observes the result. + +SEP-2787 standardizes a signed attestation of a tool **call**: it binds the +agent identity, tool name, intent, and an argument commitment into a verifiable +envelope. SEP-2817 standardizes optional, explicitly non-authoritative, +**client-asserted** input audit context (`invocationReason`, `model`, +`userIntent`, `turnId`). Both describe the input side: what was claimed, by the +party making the call. Neither carries the governing system's decision or the +recorded outcome. SEP-2817 says so directly: "server-side decision records, +stable tool-call identity, agent/session correlation, and taxonomies are left to +follow-up SEPs," and Discussion #2704 frames the server-authoritative record as +the deferred half. This SEP is that follow-up. + +Client-asserted records alone cannot satisfy Article 12 for a deployment where +the server enforces policy. A client can claim its intent and its arguments; it +cannot credibly attest that the call was allowed, why it was allowed or blocked, +or what the tool returned, because it does not own that logic and is not a +neutral observer of its own behavior. A regulator auditing an incident needs a +record signed by the enforcement point, paired to the request it answers, that +survives the client. Without a standard for that record, every governance vendor +invents an incompatible one, and cross-implementation verification (the point of +a conformance test) is impossible. + +The records are also the natural place to satisfy the rest of the Article 12 and +Article 14 surface that input audit does not reach: the allow/block/escalate +decision (human-oversight evidence under Article 14), the risk basis that drove +it (risk-management evidence under Article 9), and the post-execution outcome +that feeds post-market monitoring (Article 72). A shipping implementation already +emits all of these as audit events; this SEP standardizes the signed wire shape +so they are portable and independently verifiable. + +## Specification + +### Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in RFC 2119 and RFC 8174. + +- **Governing server**: the MCP server, or a proxy in front of it, that decides + whether a tool call runs and observes the result. It is the issuer of both + records defined here. +- **Decision record**: a signed envelope emitted before the side effect, + carrying the policy decision and its risk basis. +- **Outcome record**: a signed envelope emitted after execution, carrying the + execution status and a result commitment. +- **Request attestation**: the SEP-2787 envelope describing the tool call. It is + the anchor both records point back to. + +### Trust boundary + +This SEP is server-authoritative. Both records are signed under the governing +server's key and place their bound claims in an issuer block +(`issuerAsserted` for the decision record, `receiptAsserted` for the outcome +record), mirroring the SEP-2787 trust-surface split. The records make claims +about the **decision** and the **execution**, which only the enforcement point +can make. + +This is distinct from SEP-2787, where the issuer attests the **call** (identity, +tool, args) and the planner declares intent, and from SEP-2817, where the client +asserts input context that is explicitly not authorization evidence. A verifier +MUST treat the three as separate surfaces: a SEP-2787 attestation and a SEP-2817 +`_meta` block describe what was asked for; the records in this SEP describe what +the governing system did about it. A server MUST NOT copy client-asserted +SEP-2817 fields into the signed decision or outcome record in a way that implies +the server vouches for their truth; it MAY reference them by `turnId` for +correlation (see Pairing). + +### Canonicalization and signature + +Both records use RFC 8785 (JCS) canonical JSON. The signature is computed over +the JCS-canonical encoding of the record's blocks **excluding** the `signature` +field, and the `signature` field carries the result; it does not cover itself. +This is identical to the SEP-2787 and the shipped Vaara execution-receipt scheme, +so a verifier that already handles SEP-2787 signatures handles these records with +no new cryptographic code. + +- The `alg` field MUST be one of `ES256`, `RS256`, or `HS256`. Production + deployments crossing a trust boundary SHOULD use an asymmetric algorithm + (`ES256` or `RS256`) so verifiers need only the public key. +- IEEE-754 floats MUST NOT appear anywhere in a canonicalized body. Numeric + values that are not integers MUST be encoded as scaled integers or decimal + strings. This removes the most common source of cross-stack signature drift + and matches the Vaara JCS discipline. +- All digests are encoded as the string `sha256:`. +- All timestamps are RFC 3339 / ISO 8601 UTC strings with second precision and a + trailing `Z` (for example `2026-05-31T09:30:00Z`). + +### Argument and result commitments + +Both records reuse the SEP-2787 commitment shapes verbatim. A commitment is one +of: + +- **`ArgsRef`**: a content-addressed reference, `{ "ref": "", "digest": +"sha256:", "canonicalization": "jcs" }`. The verifier MAY fetch `ref` and + check the digest. +- **`ArgsProjection`**: a reviewed projection, `{ "projection": "", +"projectionDigest": "sha256:" }`, where `projection` is the JCS-canonical + encoding of the projection object as a UTF-8 string and `projectionDigest` is + the sha256 over those bytes. + +Commitment-only audit, where the payload never leaves the server, is expressed as +a hash-only-identity `ArgsProjection` whose `projection` is the JCS encoding of +`{ "digest": "sha256:" }` and whose embedded digest binds the underlying +value. This is the shipped `make_args_digest` behavior and is the RECOMMENDED +default for the outcome record's `resultCommitment` so that results, which may +contain personal data, are committed to without being copied into the record. + +### Decision record + +A decision record MUST be emitted by the governing server before the tool call's +side effect runs, for every governed call. It is a JSON object with the +following top-level fields. + +| Field | Type | Required | Description | +| ----------------- | ------- | -------- | ----------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1` for this SEP. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Join to the SEP-2787 attestation (see below). | +| `decisionDerived` | object | yes | The decision and its risk basis (see below). | +| `issuerAsserted` | object | yes | The governing server's issuer block (see below). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | + +**`backLink`** joins the decision to the request attestation it governs: + +| Field | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `attestationDigest` | string | yes | `sha256:` over the JCS-canonical full SEP-2787 attestation wire bytes, signature included. Pins the exact attestation instance. | +| `attestationNonce` | string | yes | Echoes the attestation's `issuerAsserted.nonce` for fast correlation. | + +If no SEP-2787 attestation exists for the call (the deployment does not run +2787), the server MUST instead bind the request by setting `attestationDigest` to +`sha256:` over the JCS-canonical encoding of the request envelope it +observed (the `tools/call` params plus `_meta`), and set `attestationNonce` to a +server-chosen per-call nonce. The binding is to the request **instance**, not +only its content. + +**`decisionDerived`** carries the decision and the basis for it: + +| Field | Type | Required | Description | +| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- | +| `decision` | string | yes | One of `allow`, `block`, `escalate`. | +| `reason` | string | no | Short human-readable basis for the decision. | +| `riskScore` | string | no | The risk estimate that drove the decision, as a decimal string (floats are prohibited on the wire). | +| `thresholdAllow` | string | no | The allow threshold in force at decision time, as a decimal string. | +| `thresholdBlock` | string | no | The block threshold in force at decision time, as a decimal string. | +| `policyId` | string | no | Identifier or digest of the policy/ruleset version that produced the decision. | +| `decidedAt` | string | yes | ISO 8601 UTC timestamp of the decision. | + +A `decision` of `escalate` means the call was held for human oversight; the +outcome record for that call will report `refused` if the human declined, or a +later decision record MAY supersede it (see Pairing). + +**`issuerAsserted`** is the governing server's signed block: + +| Field | Type | Required | Description | +| --------------- | ------ | -------- | -------------------------------------------------------------------- | +| `iss` | string | yes | Issuer identity (the governing server or proxy). | +| `sub` | string | yes | Subject the decision is about (tenant, agent, or upstream identity). | +| `iat` | string | yes | ISO 8601 UTC issuance time. | +| `nonce` | string | yes | Unique per record. | +| `secretVersion` | string | yes | Key/secret version identifier for rotation and dispatch. | +| `alg` | string | yes | MUST equal the top-level `alg`. | + +### Outcome record + +An outcome record MUST be emitted by the governing server after the governed call +completes (or is refused), and MUST be paired to a decision record for the same +call. It is a JSON object with the following top-level fields. This is the shape +already shipping as `vaara.attestation.receipt.ExecutionReceipt`. + +| Field | Type | Required | Description | +| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1`. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Same shape as the decision record's `backLink`; pins the request. | +| `outcomeDerived` | object | yes | Execution status, timing, and result commitment (see below). | +| `receiptAsserted` | object | yes | The governing server's issuer block (same shape as `issuerAsserted`, minus `expSeconds`; an outcome record is a durable record, not a capability, so it carries no `exp` and verifiers enforce no TTL). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | + +**`outcomeDerived`** carries what happened: + +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `status` | string | yes | One of `executed`, `refused`, `errored`. | +| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | +| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | + +### Pairing + +A decision record and an outcome record describe the same governed call when +both carry the same `backLink` (`attestationDigest` and `attestationNonce` +equal). A verifier that has both, plus the SEP-2787 attestation, can confirm the +full chain: the attestation pins the call; the decision record pins the policy +verdict and risk basis for that call; the outcome record pins what the call did. +This is instance-binding, not only content-binding: two byte-identical calls +produce two attestations with distinct nonces and therefore distinct +`attestationDigest` values, so a decision or outcome record cannot be replayed +against a different instance of the same call. + +For correlation with client-asserted input context (SEP-2817), a server MAY +include the SEP-2817 `turnId` as an additional, clearly client-asserted field +inside `decisionDerived` under the key `clientTurnId`. This is correlation only; +its presence in the signed body means the server records that the client claimed +this `turnId`, not that the server vouches for it. + +A superseding decision (for example, a human resolving an `escalate`) is recorded +as a new decision record with the same `backLink` and a later `decidedAt`. The +record with the latest `decidedAt` for a given `backLink` is the effective +decision; earlier ones are retained as history. Verifiers MUST NOT treat +multiple decision records for one `backLink` as a conflict. + +### Transport + +The records are signed JSON objects and are transport-agnostic. A server MAY +return the outcome record in the `tools/call` response `_meta` under the reserved +key `io.modelcontextprotocol/executionRecord`, MAY return the decision record +under `io.modelcontextprotocol/decisionRecord`, and MAY persist either to an +out-of-band audit store. When a record is too large to carry inline (a large +`ArgsRef` chain), the server SHOULD return an `ArgsRef` pointing to the stored +record. This SEP does not require any change to existing MCP message formats; the +records ride in `_meta`, consistent with SEP-414, SEP-2787, and SEP-2817. + +### JSON examples + +Decision record (allow, with risk basis): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "decisionDerived": { + "decision": "allow", + "reason": "risk below allow threshold", + "riskScore": "0.21", + "thresholdAllow": "0.40", + "thresholdBlock": "0.70", + "policyId": "sha256:3c9d4b8a", + "decidedAt": "2026-05-31T09:30:00Z", + "clientTurnId": "turn-7f3a" + }, + "issuerAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:00Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Yb7Qd2mK9sV", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3045022100ab12" +} +``` + +Outcome record (executed, result committed by digest only): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "outcomeDerived": { + "status": "executed", + "completedAt": "2026-05-31T09:30:02Z", + "resultCommitment": { + "projection": "{\"digest\":\"sha256:1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c\"}", + "projectionDigest": "sha256:aa11bb22cc33dd44ee55ff66aa77bb88cc99dd00ee11ff22aa33bb44cc55dd66" + } + }, + "receiptAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:02Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Zc8Re3nL0tW", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3046022100cd34" +} +``` + +Outcome record (refused, no result): + +```json +{ + "version": 1, + "alg": "ES256", + "backLink": { + "attestationDigest": "sha256:8f1e2c0a9b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f", + "attestationNonce": "qWZ3kP1nB8tR0aL" + }, + "outcomeDerived": { + "status": "refused", + "completedAt": "2026-05-31T09:30:01Z" + }, + "receiptAsserted": { + "alg": "ES256", + "iat": "2026-05-31T09:30:01Z", + "iss": "vaara-proxy://acme-eu", + "nonce": "Wd9Sf4oM1uX", + "secretVersion": "2026-05", + "sub": "tenant:acme/agent:billing-bot" + }, + "signature": "3045022100ef56" +} +``` + +### Verification algorithm + +A verifier holding a decision record, an outcome record, the SEP-2787 +attestation, and the governing server's public key (or shared secret for HS256) +MUST perform, and a conforming implementation MUST pass, the following checks: + +1. For each record, recompute the JCS-canonical encoding of the body with the + `signature` field removed, and verify `signature` under the issuer key for the + record's `alg`. Reject on mismatch. +2. Recompute `attestationDigest` from the SEP-2787 attestation wire bytes and + confirm both records' `backLink.attestationDigest` and + `backLink.attestationNonce` match it. Reject on mismatch. +3. Confirm the two records share the same `backLink` (they describe the same + call). +4. If `resultCommitment` is present and the verifier has the result payload, + recompute the commitment digest from the runtime result and confirm it + matches. Reject on mismatch. +5. Confirm `decisionDerived.decision` is one of `allow`, `block`, `escalate` and + `outcomeDerived.status` is one of `executed`, `refused`, `errored`. + +## Rationale + +**Two records, not one.** The decision and the outcome are made at different +times by the same party and answer different regulatory questions (Article 9 and +14 risk and oversight evidence versus Article 72 post-market outcome evidence). A +single combined record would force the server to delay signing until after +execution, losing the pre-side-effect commitment that proves the verdict was +fixed before the action ran. Splitting them, paired by `backLink`, preserves the +commit-before-execute property that is the whole point of an enforcement record. + +**Instance-binding over content-binding.** Earlier drafts in the #2704 / #2817 +discussion considered binding records to the content hash of the call alone. That +lets a record be replayed against any byte-identical call. Binding to the +SEP-2787 attestation digest (signature included) pins the exact attestation +instance, so distinct calls with identical content still produce distinct +bindings. This was the resolved position in discussion; the agent-guard author +conceded the instance-binding point on 2026-05-30, and this SEP adopts it as +normative. + +**Reusing the SEP-2787 stack.** The outcome record is the post-execution sibling +of the SEP-2787 attestation and deliberately reuses its canonicalization, signing +algorithms, commitment shapes, and issuer-block layout. A verifier that handles +SEP-2787 handles these records with no new cryptographic code, and the decision +record adds only one new block (`decisionDerived`). + +**Why server-authoritative.** A client cannot credibly attest a decision it did +not make or an outcome it is not a neutral observer of. SEP-2787 and SEP-2817 +cover the input side precisely because they are client or issuer claims about the +call. The enforcement record has to come from the enforcement point, which is why +this is a separate SEP rather than an extension of either. + +**Alternatives considered.** Carrying the decision inside the SEP-2787 +`issuerAsserted` block was rejected because SEP-2787 attests the call, not the +verdict, and overloading it would blur the trust surface that SEP made explicit. +Adopting a content-addressed `action_ref` as the primary binding field was +rejected: a content-addressed receipt id is a useful secondary index but is +content-binding, and using it as the primary join reintroduces the replay +problem instance-binding solves. The `action_ref` style is reconcilable as an +optional `ArgsRef`-shaped pointer, not as the default join. + +## Backward Compatibility + +This SEP introduces no breaking changes. Both records are new, optional `_meta` +payloads under reserved keys; servers that do not emit them and clients that do +not consume them are unaffected. The schema reuses the SEP-2787 commitment and +issuer-block shapes, so an existing SEP-2787 verifier extends to these records by +adding the `decisionDerived` block and the `outcomeDerived.status` enum, with no +change to the canonicalization or signature code. The records do not alter any +existing MCP request or response method. + +## Security Implications + +**Signing-key compromise and post-hoc backdating.** A signed record proves the +holder of the key bound those values, but a compromised key lets an adversary +mint records with any `decidedAt` / `completedAt` they choose, including +backdated ones, to fabricate a clean history. A signature alone cannot defeat +this. The defense is an **external time anchor**: periodically anchoring the head +of the append-only audit chain that carries these records to an independent, +trusted timestamp (an RFC 3161 timestamp token, or an eIDAS qualified electronic +timestamp) so that records signed after a compromise cannot be inserted before +the last anchored head without detection. The Vaara reference implementation +binds each record into a hash chain (chain version 2, with tenant identity bound +into the chained hash) and ships the external anchor in v0.48: an RFC 3161 +timestamp over the chain head, verifiable offline, with optional automatic cadence +anchoring that fails open by writing a gap marker into the chain when the +authority is unreachable. Anchoring is opt-in. The deployer configures a trusted +timestamp authority; none is bundled by default. Offline verification proves the +token was signed by the certificate it carries; establishing that certificate as +a trusted (for example eIDAS-qualified) authority is deployer policy, enforced by +pinning it. A conforming deployment under EU AI Act retention obligations SHOULD +anchor the chain head externally at a cadence proportionate to its risk. + +**Replay.** Instance-binding through the SEP-2787 attestation digest (signature +included) plus the per-record `nonce` means a record cannot be replayed against a +different call instance. Verifiers MUST reject a record whose `backLink` does not +match the attestation under verification. + +**Instance versus content binding.** As above, binding is to the attestation +instance, not only the call content. Implementations MUST NOT substitute a bare +content hash of the arguments for the `attestationDigest`, because that +reintroduces replay across byte-identical calls. + +**Personal data minimization.** Tool arguments and results can contain personal +data. The RECOMMENDED default for both `decisionDerived` (which never copies the +arguments, only a risk basis and decision) and `outcomeDerived.resultCommitment` +(commitment-only hash-only-identity projection) is that the record commits to a +digest of the value without copying the value. Where a reviewed projection is +needed for audit, servers SHOULD redact to the minimum fields necessary and +commit to the projection, never the raw payload. This keeps the signed, +long-retained record free of personal data while preserving the ability to prove, +given the original value out of band, that it is the one the record committed to. +This aligns with GDPR data minimization and storage limitation alongside the +Article 12 retention obligation. + +**Issuer identity and key distribution.** Verifiers must obtain the governing +server's public key out of band; embedding a public key in the record is a +convenience for local verification only and is not trust-establishing. The +`secretVersion` field supports key rotation; verifiers dispatch on it. + +**Denial of service.** Record emission MUST NOT block legitimate traffic; a +governing server that cannot sign SHOULD fail closed on the **decision** (do not +allow an ungoverned call) but record the signing failure, and MAY degrade outcome +recording to a logged error rather than dropping the call's result. The reference +implementation emits records on a best-effort path that never blocks the proxied +call and counts emission failures for operator alerting. + +## Prior Art Reconciled + +- **SEP-2787 (Tool call attestation, author soup-oss, Extensions Track, open).** + Attests the tool **call**: planner-declared intent, issuer-asserted identity, + payload-derived tool bindings and argument commitment. This SEP is its + post-decision and post-execution counterpart and reuses its canonicalization, + signing, commitment shapes, and trust-surface layout. The `backLink` pins a + SEP-2787 attestation. The split between what was asked (2787) and what was + decided and done (this SEP) is deliberate and preserves 2787's trust surface. + +- **SEP-2817 (AI Invocation Audit Context in Request `_meta`, author hangum, + Standards Track, draft seeking sponsor).** Standardizes optional, + client-asserted input audit context (`invocationReason`, `model`, `userIntent`, + `turnId`) and states explicitly that these fields are not authorization + evidence and that server-side decision records are left to a follow-up SEP. + This is that follow-up. Correlation with 2817 is by optionally recording the + client-asserted `turnId` as `clientTurnId`, clearly marked as a client claim. + +- **agent-guard (XuebinMa, Rust).** Models a `Guard` producing an `AuditRecord` + and an `ExecutionProof`. The two-record decision/outcome split here covers the + same ground as `AuditRecord` plus `ExecutionProof`, with the binding made + explicit and instance-scoped through the SEP-2787 attestation digest rather + than left to a content hash. The instance-binding position was conceded by the + agent-guard author in discussion on 2026-05-30 and is adopted here as + normative. + +- **Content-addressed receipt identifiers.** Some designs identify a record by a + content-addressed id of the form `action_ref = sha256(JCS(...))` over a + description of the action. This SEP does not adopt a content-addressed id as the + default join field, because it is content-binding and reintroduces + cross-instance replay when used as the primary binding. Such a pointer is + reconcilable as an optional `ArgsRef`-shaped reference (the `ref` carries the id + and the `digest` pins it), not as the normative pairing key, which remains the + instance-scoped `backLink`. + +## Reference Implementation + +The wire schema in this SEP is the shape shipping in the Vaara MCP proxy +(v0.48; the receipt library landed in v0.42). Relevant modules: + +- `vaara/attestation/_receipt_types.py`: the `ExecutionReceipt` envelope + (`version`, `alg`, `backLink`, `outcomeDerived`, `receiptAsserted`, + `signature`), `OutcomeDerived` with `status` constrained to `executed` / + `refused` / `errored`, and `BackLink` (`attestationDigest`, + `attestationNonce`). This is the outcome record of this SEP, byte for byte. +- `vaara/attestation/_receipt_emit.py`: builds, JCS-canonicalizes (signing input + excludes `signature`), and signs the outcome record; verifies the signature. +- `vaara/attestation/_receipt_verifier.py`: `make_back_link` / `verify_back_link` + and `attestation_digest` (sha256 over the JCS-canonical full SEP-2787 wire + bytes, signature included): the instance-binding join. +- `vaara/attestation/decision.py`: the decision record of this SEP. + `DecisionRecord` / `DecisionDerived`, `emit_decision_record`, + `verify_decision_signature`, `verify_decision_back_link`, and + `records_paired` (the decision-and-outcome join). Reuses the receipt's + back-link, the issuer-block layout, and the shared signing stack unchanged, + so the decision record adds the `decisionDerived` block and no new crypto. +- `vaara/attestation/_sep2787_types.py` and `_sep2787_canonical.py`: the shared + commitment shapes (`ArgsRef`, `ArgsProjection`, `make_args_digest`), the + issuer-block layout, RFC 8785 canonicalization with float rejection, and the + signing stack (`ES256` / `RS256` / `HS256`) reused unchanged. +- `vaara/integrations/_mcp_attest.py` and `mcp_proxy.py`: the proxy emits the + SEP-2787 attestation before the call and the paired outcome record after it, + on a best-effort path that never blocks proxied traffic. +- `vaara/audit/trail.py`: the append-only, hash-chained audit trail the records + are written into; chain version 2 binds `tenant_id` and `chain_version` into + the chained hash so a record cannot be silently re-attributed to another + tenant. +- `vaara/audit/timeanchor.py` and `AuditTrail.enable_auto_anchor`: the external + time anchor over the chain head (Security Implications), shipped in v0.48 as an + RFC 3161 client plus an offline verifier, with optional automatic cadence + anchoring that writes an `ANCHOR_GAP` marker into the chain when the authority + is unreachable. + +**Bridge from the shipped audit decision.** The audit trail already records the +pre-execution decision as a hash-chained `CommitPayload` +(`vaara/audit/receipts.py`: `decision`, `risk_score`, `threshold_allow`, +`threshold_deny`, `decided_at`). `decision_derived_from_commit` maps that +payload onto the signed `decisionDerived` wire shape. The mapping is mechanical +but not a rename: the verdict vocabulary is normalized (the audit layer records +`deny`, the wire enum uses `block`; the review family maps to `escalate`), the +float risk basis becomes decimal strings (floats are banned on the wire), and +the epoch decision time becomes an ISO 8601 UTC string. `policy_id`, +`clientTurnId`, and `reason` are not carried on the commit payload and are +supplied by the caller when available. This keeps the long-retained signed +record free of the float canonicalization drift that the hash-chained payload +tolerated internally. + +## Test Vectors + +The Vaara conformance vectors for the SEP-2787 canonicalization and signature +(`modelcontextprotocol/modelcontextprotocol#2789`) cover the JCS encoding, float +rejection, and the detached-signature scheme that both records in this SEP reuse. +A standard-library-only checker (no Vaara import; `cryptography` for the +asymmetric case) verifies a signed export offline, mirroring the +`scripts/verify_vaara_trail.py` approach. For Standards Track finalization, this +SEP will add: + +- JCS canonical vectors for the `decisionDerived` block and the + `outcomeDerived.status` enum, in the same vector format as #2789. +- A paired decision-plus-outcome fixture with its SEP-2787 attestation, exercising + the full verification algorithm above, including a replay-rejection case + (mismatched `backLink`) and a superseding-decision case (two decision records, + same `backLink`, distinct `decidedAt`). +- A `sep-XXXX.yaml` traceability file mapping each MUST / MUST NOT and + SHOULD / SHOULD NOT in the Specification to a conformance check ID, as required + for Standards Track SEPs reaching Final. + +## Acknowledgments + +This proposal builds directly on SEP-2787 (soup-oss) and SEP-2817 (hangum), and +on the server-authoritative record direction set out in Discussion #2704. The +instance-binding versus content-binding discussion with the agent-guard author +(XuebinMa) shaped the normative pairing rule. diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index ff09a3642..47b6d2a31 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -12,56 +12,57 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary +- **Draft**: 5 - **Final**: 32 -- **Draft**: 4 - **Accepted**: 4 - **In-review**: 1 ## All SEPs -| SEP | Title | Status | Type | Created | -| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------- | ---------- | -| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | -| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Draft | Process | 2026-04-17 | -| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | -| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Accepted | Standards Track | 2025-06-18 | -| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | -| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Accepted | Standards Track | 2026-04-09 | -| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Draft | Process | 2026-03-27 | -| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | In-Review | Standards Track | 2026-03-25 | -| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Accepted | Standards Track | 2026-02-03 | -| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | -| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | -| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Accepted | Standards Track | 2026-02-04 | -| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Draft | Standards Track | 2026-01-28 | -| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | -| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | -| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | -| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Draft | Standards Track | 2026-01-06 | -| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | -| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | -| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | -| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | -| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | -| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | -| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | -| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | -| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | -| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | -| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | -| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | -| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | -| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | -| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | -| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | -| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | -| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | -| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | -| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | -| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | -| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | -| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | -| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | +| SEP | Title | Status | Type | Created | +| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------- | ---------- | +| [SEP-2828](/seps/2828-server-side-signed-execution-record) | Server-Side Signed Execution Record for MCP Tool Calls | Draft | Standards Track (Extensions Track) | 2026-05-31 | +| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | +| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Draft | Process | 2026-04-17 | +| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | +| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Accepted | Standards Track | 2025-06-18 | +| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | +| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Accepted | Standards Track | 2026-04-09 | +| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Draft | Process | 2026-03-27 | +| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | In-Review | Standards Track | 2026-03-25 | +| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Accepted | Standards Track | 2026-02-03 | +| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | +| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | +| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Accepted | Standards Track | 2026-02-04 | +| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Draft | Standards Track | 2026-01-28 | +| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | +| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | +| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | +| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Draft | Standards Track | 2026-01-06 | +| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | +| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | +| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | +| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | +| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | +| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | +| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | +| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | +| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | +| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | +| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | +| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | +| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | +| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | +| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | +| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | +| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | +| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | +| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | +| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | +| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | +| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | +| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | +| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | ## SEP Status Definitions From 5e411b530015f9d799e14240edca559ee2c7c945 Mon Sep 17 00:00:00 2001 From: vaaraio Date: Sun, 31 May 2026 14:03:57 +0300 Subject: [PATCH 4/9] docs(seps): prettier-format SEP-2828 Co-Authored-By: Claude Opus 4.8 --- ...828-server-side-signed-execution-record.md | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/seps/2828-server-side-signed-execution-record.md b/seps/2828-server-side-signed-execution-record.md index 480a17087..d62d0237a 100644 --- a/seps/2828-server-side-signed-execution-record.md +++ b/seps/2828-server-side-signed-execution-record.md @@ -1,6 +1,5 @@ # SEP-2828: Server-Side Signed Execution Record for MCP Tool Calls - - **Status**: Draft - **Type**: Standards Track (Extensions Track) - **Created**: 2026-05-31 @@ -137,10 +136,10 @@ Both records reuse the SEP-2787 commitment shapes verbatim. A commitment is one of: - **`ArgsRef`**: a content-addressed reference, `{ "ref": "", "digest": - "sha256:", "canonicalization": "jcs" }`. The verifier MAY fetch `ref` and +"sha256:", "canonicalization": "jcs" }`. The verifier MAY fetch `ref` and check the digest. - **`ArgsProjection`**: a reviewed projection, `{ "projection": "", - "projectionDigest": "sha256:" }`, where `projection` is the JCS-canonical +"projectionDigest": "sha256:" }`, where `projection` is the JCS-canonical encoding of the projection object as a UTF-8 string and `projectionDigest` is the sha256 over those bytes. @@ -157,21 +156,21 @@ A decision record MUST be emitted by the governing server before the tool call's side effect runs, for every governed call. It is a JSON object with the following top-level fields. -| Field | Type | Required | Description | -| ---------------- | -------- | -------- | --------------------------------------------------------------------------- | -| `version` | integer | yes | Record schema version. `1` for this SEP. | -| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | -| `backLink` | object | yes | Join to the SEP-2787 attestation (see below). | -| `decisionDerived`| object | yes | The decision and its risk basis (see below). | -| `issuerAsserted` | object | yes | The governing server's issuer block (see below). | -| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | +| Field | Type | Required | Description | +| ----------------- | ------- | -------- | ----------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1` for this SEP. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Join to the SEP-2787 attestation (see below). | +| `decisionDerived` | object | yes | The decision and its risk basis (see below). | +| `issuerAsserted` | object | yes | The governing server's issuer block (see below). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | **`backLink`** joins the decision to the request attestation it governs: -| Field | Type | Required | Description | -| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------ | -| `attestationDigest`| string | yes | `sha256:` over the JCS-canonical full SEP-2787 attestation wire bytes, signature included. Pins the exact attestation instance. | -| `attestationNonce` | string | yes | Echoes the attestation's `issuerAsserted.nonce` for fast correlation. | +| Field | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `attestationDigest` | string | yes | `sha256:` over the JCS-canonical full SEP-2787 attestation wire bytes, signature included. Pins the exact attestation instance. | +| `attestationNonce` | string | yes | Echoes the attestation's `issuerAsserted.nonce` for fast correlation. | If no SEP-2787 attestation exists for the call (the deployment does not run 2787), the server MUST instead bind the request by setting `attestationDigest` to @@ -182,15 +181,15 @@ only its content. **`decisionDerived`** carries the decision and the basis for it: -| Field | Type | Required | Description | -| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------ | -| `decision` | string | yes | One of `allow`, `block`, `escalate`. | -| `reason` | string | no | Short human-readable basis for the decision. | -| `riskScore` | string | no | The risk estimate that drove the decision, as a decimal string (floats are prohibited on the wire). | -| `thresholdAllow` | string | no | The allow threshold in force at decision time, as a decimal string. | -| `thresholdBlock` | string | no | The block threshold in force at decision time, as a decimal string. | -| `policyId` | string | no | Identifier or digest of the policy/ruleset version that produced the decision. | -| `decidedAt` | string | yes | ISO 8601 UTC timestamp of the decision. | +| Field | Type | Required | Description | +| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- | +| `decision` | string | yes | One of `allow`, `block`, `escalate`. | +| `reason` | string | no | Short human-readable basis for the decision. | +| `riskScore` | string | no | The risk estimate that drove the decision, as a decimal string (floats are prohibited on the wire). | +| `thresholdAllow` | string | no | The allow threshold in force at decision time, as a decimal string. | +| `thresholdBlock` | string | no | The block threshold in force at decision time, as a decimal string. | +| `policyId` | string | no | Identifier or digest of the policy/ruleset version that produced the decision. | +| `decidedAt` | string | yes | ISO 8601 UTC timestamp of the decision. | A `decision` of `escalate` means the call was held for human oversight; the outcome record for that call will report `refused` if the human declined, or a @@ -198,14 +197,14 @@ later decision record MAY supersede it (see Pairing). **`issuerAsserted`** is the governing server's signed block: -| Field | Type | Required | Description | -| -------------- | ------- | -------- | --------------------------------------------------------------------- | -| `iss` | string | yes | Issuer identity (the governing server or proxy). | -| `sub` | string | yes | Subject the decision is about (tenant, agent, or upstream identity). | -| `iat` | string | yes | ISO 8601 UTC issuance time. | -| `nonce` | string | yes | Unique per record. | -| `secretVersion`| string | yes | Key/secret version identifier for rotation and dispatch. | -| `alg` | string | yes | MUST equal the top-level `alg`. | +| Field | Type | Required | Description | +| --------------- | ------ | -------- | -------------------------------------------------------------------- | +| `iss` | string | yes | Issuer identity (the governing server or proxy). | +| `sub` | string | yes | Subject the decision is about (tenant, agent, or upstream identity). | +| `iat` | string | yes | ISO 8601 UTC issuance time. | +| `nonce` | string | yes | Unique per record. | +| `secretVersion` | string | yes | Key/secret version identifier for rotation and dispatch. | +| `alg` | string | yes | MUST equal the top-level `alg`. | ### Outcome record @@ -214,21 +213,21 @@ completes (or is refused), and MUST be paired to a decision record for the same call. It is a JSON object with the following top-level fields. This is the shape already shipping as `vaara.attestation.receipt.ExecutionReceipt`. -| Field | Type | Required | Description | -| ---------------- | -------- | -------- | ------------------------------------------------------------------- | -| `version` | integer | yes | Record schema version. `1`. | -| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | -| `backLink` | object | yes | Same shape as the decision record's `backLink`; pins the request. | -| `outcomeDerived` | object | yes | Execution status, timing, and result commitment (see below). | -| `receiptAsserted`| object | yes | The governing server's issuer block (same shape as `issuerAsserted`, minus `expSeconds`; an outcome record is a durable record, not a capability, so it carries no `exp` and verifiers enforce no TTL). | -| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | +| Field | Type | Required | Description | +| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `version` | integer | yes | Record schema version. `1`. | +| `alg` | string | yes | `ES256`, `RS256`, or `HS256`. | +| `backLink` | object | yes | Same shape as the decision record's `backLink`; pins the request. | +| `outcomeDerived` | object | yes | Execution status, timing, and result commitment (see below). | +| `receiptAsserted` | object | yes | The governing server's issuer block (same shape as `issuerAsserted`, minus `expSeconds`; an outcome record is a durable record, not a capability, so it carries no `exp` and verifiers enforce no TTL). | +| `signature` | string | yes | Detached signature over the JCS body excluding `signature`. | **`outcomeDerived`** carries what happened: -| Field | Type | Required | Description | -| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------ | -| `status` | string | yes | One of `executed`, `refused`, `errored`. | -| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `status` | string | yes | One of `executed`, `refused`, `errored`. | +| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | | `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | ### Pairing From f1ba1047aee0b2354ee76fc5d8847a7bea87949d Mon Sep 17 00:00:00 2001 From: Henri Sirkkavaara Date: Mon, 1 Jun 2026 13:53:58 +0300 Subject: [PATCH 5/9] docs(sep-2828): set author to Henri Sirkkavaara, Vaara --- seps/2828-server-side-signed-execution-record.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seps/2828-server-side-signed-execution-record.md b/seps/2828-server-side-signed-execution-record.md index d62d0237a..ba4313831 100644 --- a/seps/2828-server-side-signed-execution-record.md +++ b/seps/2828-server-side-signed-execution-record.md @@ -3,7 +3,7 @@ - **Status**: Draft - **Type**: Standards Track (Extensions Track) - **Created**: 2026-05-31 -- **Author(s)**: vaaraio (@vaaraio) +- **Author(s)**: Henri Sirkkavaara (@vaaraio), Vaara - **Sponsor**: None (seeking sponsor) - **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2828 - **Requires**: SEP-2787 (Tool call attestation) From bca7d71f2e15955018a99f5bab5477b5e4d0d964 Mon Sep 17 00:00:00 2001 From: Henri Sirkkavaara Date: Mon, 1 Jun 2026 13:54:00 +0300 Subject: [PATCH 6/9] docs(sep-2828): set author to Henri Sirkkavaara, Vaara --- docs/seps/2828-server-side-signed-execution-record.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/seps/2828-server-side-signed-execution-record.mdx b/docs/seps/2828-server-side-signed-execution-record.mdx index 5859d2f1f..09be40a93 100644 --- a/docs/seps/2828-server-side-signed-execution-record.mdx +++ b/docs/seps/2828-server-side-signed-execution-record.mdx @@ -20,7 +20,7 @@ description: "Server-Side Signed Execution Record for MCP Tool Calls" | **Status** | Draft | | **Type** | Standards Track (Extensions Track) | | **Created** | 2026-05-31 | -| **Author(s)** | vaaraio ([@vaaraio](https://github.com/vaaraio)) | +| **Author(s)** | Henri Sirkkavaara ([@vaaraio](https://github.com/vaaraio)), Vaara | | **Sponsor** | None (seeking sponsor) | | **PR** | [#2828](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2828) | From 84ada297d789667dc8fc1de7bf0cc94d4d5911fd Mon Sep 17 00:00:00 2001 From: Henri Sirkkavaara Date: Mon, 1 Jun 2026 14:50:23 +0300 Subject: [PATCH 7/9] docs(sep-2828): fix metadata table alignment for prettier --- docs/seps/2828-server-side-signed-execution-record.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/seps/2828-server-side-signed-execution-record.mdx b/docs/seps/2828-server-side-signed-execution-record.mdx index 09be40a93..072ab3a0c 100644 --- a/docs/seps/2828-server-side-signed-execution-record.mdx +++ b/docs/seps/2828-server-side-signed-execution-record.mdx @@ -20,7 +20,7 @@ description: "Server-Side Signed Execution Record for MCP Tool Calls" | **Status** | Draft | | **Type** | Standards Track (Extensions Track) | | **Created** | 2026-05-31 | -| **Author(s)** | Henri Sirkkavaara ([@vaaraio](https://github.com/vaaraio)), Vaara | +| **Author(s)** | Henri Sirkkavaara ([@vaaraio](https://github.com/vaaraio)), Vaara | | **Sponsor** | None (seeking sponsor) | | **PR** | [#2828](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2828) | From 76f9fa9788793c3633548611ce6e67a92fc72f25 Mon Sep 17 00:00:00 2001 From: vaaraio <267591518+vaaraio@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:04:45 +0300 Subject: [PATCH 8/9] docs(sep-2828): add Check B outcome-to-decision content binding Bring the pairing rule in line with the implemented and conformance-tested behaviour: - outcomeDerived.decisionDigest: sha256 over the JCS-canonical full signed decision-record wire bytes the outcome was produced under. A conforming emitter MUST set it; pairing fails without it. - Pairing now states both checks. Check A (instance anchor) is the shared backLink; Check B (the normative pairing) is the outcome's decisionDigest equalling this decision's digest. Check A alone admits a different decision taken under the same attestation (an escalate and the verdict that supersedes it share the attestation); Check B pins which decision's content the outcome answers. - Supersession: when two decision records for one backLink carry the same decidedAt, the tie breaks on the lexicographically lowest issuerAsserted.nonce, so every verifier selects the same effective decision with no clock authority. - Test Vectors: the decision-and-outcome pairing suite is now published (tests/vectors/decision_pairing_v0/), driven by a standard-library-only walker with a per-case expected verdict, so an independent emitter or consumer can run it against its own implementation. --- ...28-server-side-signed-execution-record.mdx | 80 ++++++++++++++----- ...828-server-side-signed-execution-record.md | 80 ++++++++++++++----- 2 files changed, 116 insertions(+), 44 deletions(-) diff --git a/docs/seps/2828-server-side-signed-execution-record.mdx b/docs/seps/2828-server-side-signed-execution-record.mdx index 072ab3a0c..50fd372ec 100644 --- a/docs/seps/2828-server-side-signed-execution-record.mdx +++ b/docs/seps/2828-server-side-signed-execution-record.mdx @@ -239,23 +239,37 @@ already shipping as `vaara.attestation.receipt.ExecutionReceipt`. **`outcomeDerived`** carries what happened: -| Field | Type | Required | Description | -| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `status` | string | yes | One of `executed`, `refused`, `errored`. | -| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | -| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `status` | string | yes | One of `executed`, `refused`, `errored`. | +| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | +| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | +| `decisionDigest` | string | yes\* | `sha256:` over the JCS-canonical full decision-record wire bytes (signature included) the outcome was produced under. This is the Check B (outcome-to-decision) binding. \*Optional on the wire for backward parsing of records emitted before this field, but a conforming emitter MUST set it and pairing fails without it (see Pairing). | ### Pairing -A decision record and an outcome record describe the same governed call when -both carry the same `backLink` (`attestationDigest` and `attestationNonce` -equal). A verifier that has both, plus the SEP-2787 attestation, can confirm the -full chain: the attestation pins the call; the decision record pins the policy -verdict and risk basis for that call; the outcome record pins what the call did. -This is instance-binding, not only content-binding: two byte-identical calls -produce two attestations with distinct nonces and therefore distinct -`attestationDigest` values, so a decision or outcome record cannot be replayed -against a different instance of the same call. +A decision record and an outcome record pair when **both** of the following +hold: + +- **Check A (instance anchor).** Both records carry the same `backLink` + (`attestationDigest` and `attestationNonce` equal). This is instance-binding, + not only content-binding: two byte-identical calls produce two attestations + with distinct nonces and therefore distinct `attestationDigest` values, so a + record cannot be replayed against a different instance of the same call. In the + no-attestation fallback, the shared `backLink` is over the request envelope + instead, and Check A anchors on that. +- **Check B (outcome-to-decision digest, the normative pairing).** The outcome + record's `outcomeDerived.decisionDigest` equals `sha256:` over the JCS + canonical full wire bytes of _this_ decision record. Check A alone admits a + different decision taken under the same attestation instance (an `escalate` and + the human verdict that supersedes it both share the attestation); Check B pins + which decision's content the outcome answers. An outcome record without + `decisionDigest` does not pair: content binding is mandatory, not best-effort. + +A verifier that has both records, plus the SEP-2787 attestation, can then confirm +the full chain: the attestation pins the call; the decision record pins the +policy verdict and risk basis; the outcome record pins what the call did and the +decision it ran under. For correlation with client-asserted input context (SEP-2817), a server MAY include the SEP-2817 `turnId` as an additional, clearly client-asserted field @@ -266,8 +280,13 @@ this `turnId`, not that the server vouches for it. A superseding decision (for example, a human resolving an `escalate`) is recorded as a new decision record with the same `backLink` and a later `decidedAt`. The record with the latest `decidedAt` for a given `backLink` is the effective -decision; earlier ones are retained as history. Verifiers MUST NOT treat -multiple decision records for one `backLink` as a conflict. +decision; earlier ones are retained as history. When two records for one +`backLink` carry the **same** `decidedAt`, the tie MUST break deterministically: +the effective record is the one whose `issuerAsserted.nonce` is lexicographically +lowest. This gives every verifier the same winner with no clock authority. +Verifiers MUST NOT treat multiple decision records for one `backLink` as a +conflict. The outcome record's `decisionDigest` (Check B) identifies which +decision in this set the call actually ran under. ### Transport @@ -590,15 +609,32 @@ The Vaara conformance vectors for the SEP-2787 canonicalization and signature rejection, and the detached-signature scheme that both records in this SEP reuse. A standard-library-only checker (no Vaara import; `cryptography` for the asymmetric case) verifies a signed export offline, mirroring the -`scripts/verify_vaara_trail.py` approach. For Standards Track finalization, this -SEP will add: +`scripts/verify_vaara_trail.py` approach. + +The decision-and-outcome pairing has its own published vector suite +(`tests/vectors/decision_pairing_v0/` in the Vaara repository), driven by a +standard-library-only walker (no Vaara import) that asserts an expected verdict +per case. The suite exercises the full pairing algorithm above: + +- a valid decision-plus-outcome pair with its SEP-2787 attestation (`executed`); +- two Check A substitution negatives that MUST fail: a substituted attestation + back-link, and a mismatched pairing nonce; +- a Check B negative: a substituted decision under a shared attestation, where + Check A passes but the outcome's `decisionDigest` commits to a different + decision than the one presented; +- the no-attestation fallback, where the shared `backLink` is over the request + envelope, with a replayed-receipt rejection; +- a decision-only `escalate` with no outcome record yet emitted; +- a supersession tie: two decision records with the same `backLink` and equal + `decidedAt`, resolved to the lexicographically lowest `issuerAsserted.nonce`. + +Because each case carries its expected verdict and the walker takes no Vaara +dependency, an independent emitter or consumer can run the same suite against its +own implementation. For Standards Track finalization, this SEP will additionally +add: - JCS canonical vectors for the `decisionDerived` block and the `outcomeDerived.status` enum, in the same vector format as #2789. -- A paired decision-plus-outcome fixture with its SEP-2787 attestation, exercising - the full verification algorithm above, including a replay-rejection case - (mismatched `backLink`) and a superseding-decision case (two decision records, - same `backLink`, distinct `decidedAt`). - A `sep-XXXX.yaml` traceability file mapping each MUST / MUST NOT and SHOULD / SHOULD NOT in the Specification to a conformance check ID, as required for Standards Track SEPs reaching Final. diff --git a/seps/2828-server-side-signed-execution-record.md b/seps/2828-server-side-signed-execution-record.md index ba4313831..cee6dd130 100644 --- a/seps/2828-server-side-signed-execution-record.md +++ b/seps/2828-server-side-signed-execution-record.md @@ -224,23 +224,37 @@ already shipping as `vaara.attestation.receipt.ExecutionReceipt`. **`outcomeDerived`** carries what happened: -| Field | Type | Required | Description | -| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `status` | string | yes | One of `executed`, `refused`, `errored`. | -| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | -| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | +| Field | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `status` | string | yes | One of `executed`, `refused`, `errored`. | +| `completedAt` | string | yes | ISO 8601 UTC completion (or refusal) time. | +| `resultCommitment` | object | no | An `ArgsRef` or `ArgsProjection` over the result (executed) or error object (errored). Absent for `refused`, which has no result. RECOMMENDED to use the commitment-only hash-only-identity projection so result payloads, which may contain personal data, are not copied into the record. | +| `decisionDigest` | string | yes\* | `sha256:` over the JCS-canonical full decision-record wire bytes (signature included) the outcome was produced under. This is the Check B (outcome-to-decision) binding. \*Optional on the wire for backward parsing of records emitted before this field, but a conforming emitter MUST set it and pairing fails without it (see Pairing). | ### Pairing -A decision record and an outcome record describe the same governed call when -both carry the same `backLink` (`attestationDigest` and `attestationNonce` -equal). A verifier that has both, plus the SEP-2787 attestation, can confirm the -full chain: the attestation pins the call; the decision record pins the policy -verdict and risk basis for that call; the outcome record pins what the call did. -This is instance-binding, not only content-binding: two byte-identical calls -produce two attestations with distinct nonces and therefore distinct -`attestationDigest` values, so a decision or outcome record cannot be replayed -against a different instance of the same call. +A decision record and an outcome record pair when **both** of the following +hold: + +- **Check A (instance anchor).** Both records carry the same `backLink` + (`attestationDigest` and `attestationNonce` equal). This is instance-binding, + not only content-binding: two byte-identical calls produce two attestations + with distinct nonces and therefore distinct `attestationDigest` values, so a + record cannot be replayed against a different instance of the same call. In the + no-attestation fallback, the shared `backLink` is over the request envelope + instead, and Check A anchors on that. +- **Check B (outcome-to-decision digest, the normative pairing).** The outcome + record's `outcomeDerived.decisionDigest` equals `sha256:` over the JCS + canonical full wire bytes of _this_ decision record. Check A alone admits a + different decision taken under the same attestation instance (an `escalate` and + the human verdict that supersedes it both share the attestation); Check B pins + which decision's content the outcome answers. An outcome record without + `decisionDigest` does not pair: content binding is mandatory, not best-effort. + +A verifier that has both records, plus the SEP-2787 attestation, can then confirm +the full chain: the attestation pins the call; the decision record pins the +policy verdict and risk basis; the outcome record pins what the call did and the +decision it ran under. For correlation with client-asserted input context (SEP-2817), a server MAY include the SEP-2817 `turnId` as an additional, clearly client-asserted field @@ -251,8 +265,13 @@ this `turnId`, not that the server vouches for it. A superseding decision (for example, a human resolving an `escalate`) is recorded as a new decision record with the same `backLink` and a later `decidedAt`. The record with the latest `decidedAt` for a given `backLink` is the effective -decision; earlier ones are retained as history. Verifiers MUST NOT treat -multiple decision records for one `backLink` as a conflict. +decision; earlier ones are retained as history. When two records for one +`backLink` carry the **same** `decidedAt`, the tie MUST break deterministically: +the effective record is the one whose `issuerAsserted.nonce` is lexicographically +lowest. This gives every verifier the same winner with no clock authority. +Verifiers MUST NOT treat multiple decision records for one `backLink` as a +conflict. The outcome record's `decisionDigest` (Check B) identifies which +decision in this set the call actually ran under. ### Transport @@ -575,15 +594,32 @@ The Vaara conformance vectors for the SEP-2787 canonicalization and signature rejection, and the detached-signature scheme that both records in this SEP reuse. A standard-library-only checker (no Vaara import; `cryptography` for the asymmetric case) verifies a signed export offline, mirroring the -`scripts/verify_vaara_trail.py` approach. For Standards Track finalization, this -SEP will add: +`scripts/verify_vaara_trail.py` approach. + +The decision-and-outcome pairing has its own published vector suite +(`tests/vectors/decision_pairing_v0/` in the Vaara repository), driven by a +standard-library-only walker (no Vaara import) that asserts an expected verdict +per case. The suite exercises the full pairing algorithm above: + +- a valid decision-plus-outcome pair with its SEP-2787 attestation (`executed`); +- two Check A substitution negatives that MUST fail: a substituted attestation + back-link, and a mismatched pairing nonce; +- a Check B negative: a substituted decision under a shared attestation, where + Check A passes but the outcome's `decisionDigest` commits to a different + decision than the one presented; +- the no-attestation fallback, where the shared `backLink` is over the request + envelope, with a replayed-receipt rejection; +- a decision-only `escalate` with no outcome record yet emitted; +- a supersession tie: two decision records with the same `backLink` and equal + `decidedAt`, resolved to the lexicographically lowest `issuerAsserted.nonce`. + +Because each case carries its expected verdict and the walker takes no Vaara +dependency, an independent emitter or consumer can run the same suite against its +own implementation. For Standards Track finalization, this SEP will additionally +add: - JCS canonical vectors for the `decisionDerived` block and the `outcomeDerived.status` enum, in the same vector format as #2789. -- A paired decision-plus-outcome fixture with its SEP-2787 attestation, exercising - the full verification algorithm above, including a replay-rejection case - (mismatched `backLink`) and a superseding-decision case (two decision records, - same `backLink`, distinct `decidedAt`). - A `sep-XXXX.yaml` traceability file mapping each MUST / MUST NOT and SHOULD / SHOULD NOT in the Specification to a conformance check ID, as required for Standards Track SEPs reaching Final. From bbdecbc99defd4fe4bdd99c000434389387046f6 Mon Sep 17 00:00:00 2001 From: vaaraio Date: Sun, 28 Jun 2026 13:12:44 +0300 Subject: [PATCH 9/9] docs: regenerate SEP index and docs.json via generate:seps --- docs/docs.json | 7 +++- docs/seps/index.mdx | 86 ++++++++++++++++++++++----------------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index 66b5f7cde..7619c6f89 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -456,7 +456,12 @@ "seps/2575-stateless-mcp", "seps/2577-deprecate-roots-sampling-and-logging", "seps/2596-spec-feature-lifecycle-and-deprecation", - "seps/2663-tasks-extension", + "seps/2663-tasks-extension" + ] + }, + { + "group": "Draft", + "pages": [ "seps/2828-server-side-signed-execution-record" ] } diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index 36b1cef70..631c0075e 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -17,50 +17,50 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## All SEPs -| SEP | Title | Status | Type | Created | -| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------- | ---------------- | ---------- | +| SEP | Title | Status | Type | Created | +| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------- | ---------------------------------- | ---------- | | [SEP-2828](/seps/2828-server-side-signed-execution-record) | Server-Side Signed Execution Record for MCP Tool Calls | Draft | Standards Track (Extensions Track) | 2026-05-31 | -| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | -| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Final | Process | 2026-04-17 | -| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | -| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Final | Standards Track | 2025-06-18 | -| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | -| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Final | Standards Track | 2026-04-09 | -| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Final | Process | 2026-03-27 | -| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | Final | Standards Track | 2026-03-25 | -| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Final | Standards Track | 2026-02-03 | -| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | -| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | -| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Final | Standards Track | 2026-02-04 | -| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Final | Standards Track | 2026-01-28 | -| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | -| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | -| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | -| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Final | Standards Track | 2026-01-06 | -| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | -| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | -| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | -| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | -| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | -| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | -| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | -| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | -| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | -| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | -| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | -| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | -| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | -| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | -| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | -| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | -| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | -| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | -| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | -| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | -| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | -| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | -| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | -| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | +| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | +| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Final | Process | 2026-04-17 | +| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | +| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Final | Standards Track | 2025-06-18 | +| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | +| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Final | Standards Track | 2026-04-09 | +| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Final | Process | 2026-03-27 | +| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | Final | Standards Track | 2026-03-25 | +| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Final | Standards Track | 2026-02-03 | +| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | +| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | +| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Final | Standards Track | 2026-02-04 | +| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Final | Standards Track | 2026-01-28 | +| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | +| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | +| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | +| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Final | Standards Track | 2026-01-06 | +| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | +| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | +| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | +| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | +| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | +| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | +| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | +| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | +| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | +| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | +| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | +| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | +| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | +| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | +| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | +| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | +| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | +| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | +| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | +| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | +| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | +| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | +| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | +| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | ## SEP Status Definitions