Proposal: Deterministic Per-Primitive Digests#45
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adopt per-primitive ETags (io.modelcontextprotocol/digest) in each primitive's _meta with optimistic If-Match-style reflection (expectedDigest) and DigestChangedError; drop the aggregate surface digest. Lead with production deployment as the fundamental case, make capability advertisement optional, inherit cacheScope, and add optional HTTP header mirroring for rolling upgrades.
|
Reworked this proposal in response to the Caching & Optimization track discussion. Moved to a digest per primitive (an opaque ETag on each tool/prompt/resource/template, in its Optimistic conditional requests. A client MAY echo Fits the caching picture. The digest is the call-time validator; Capabilities as an optional optimization (per @mark): advertisement is a hint, not a precondition. A client uses the digest on sight and MAY send Production deployment is the headline case. Reframed the motivation around the thing we've never had a working spec answer for — a server redeploys and its primitives (incl. Rolling upgrades / HTTP. Added optional HTTP header mirroring for single-primitive ops so intermediaries can revalidate within a still-fresh TTL window (open question: reuse Open questions updated accordingly (header shape, resource content vs definition ETags, canonicalization normativity, reflection scope, deploy-time retry storms). |
| @@ -0,0 +1,650 @@ | |||
| # SEP-XXXX: Deterministic Per-Primitive Digests | |||
There was a problem hiding this comment.
What is the definition of "deterministic" in the content of this SEP?
Deterministic is quite a difficult term.
There was a problem hiding this comment.
A common definition of deterministic is given here:
https://en.wikipedia.org/wiki/Deterministic_finite_automaton
But I can't make the link to your PR.
There was a problem hiding this comment.
This is the kind of determinism I mean: https://en.wikipedia.org/wiki/Deterministic_algorithm
In computer science, a deterministic algorithm is an algorithm that, given a particular input, will always produce the same output, with the underlying machine always passing through the same sequence of states.
| primitive surface should be able to reflect that reality deterministically. Yet | ||
| the existing mechanisms do not deliver it for real, scaled deployments: | ||
|
|
||
| - **TTL alone is non-deterministic with respect to deploys.** A client that |
There was a problem hiding this comment.
What does that mean? IMHO, Time to live is not related to determinism.
There was a problem hiding this comment.
Doy you mean a well-ordered lists of identifiers (ordered by time)?
There was a problem hiding this comment.
Agreed, it's misused in this application, should rephrase.
| ## Abstract | ||
|
|
||
| This SEP gives every caller-visible primitive — each tool, prompt, resource, and | ||
| resource template — a deterministic, opaque **digest** of its own definition, |
…t proposal Resolve the seven open questions per WG review (header story: ETag for lists, Mcp-* header for calls; MAY-equivalent canonicalization; complementary resource content revalidation; protocol-version changes kept observable; reflection on all single-primitive methods; soft process-and-flag posture for rolling deploys with draining guidance; single primitiveDigests capability). Rewrite Open Questions into resolved vs. still-open.
|
Folded in the resolutions to all seven open questions from WG review (thanks @-Shaun and @-Mark). Summary of what changed:
The Open Questions section now separates the resolved items from the few genuine residuals (exact |
Firm up the three residual open questions and replace Open Questions with a settled Design Decisions recap: normative Mcp-Digest / Mcp-Expected-Digest header names; reserved io.modelcontextprotocol/contentDigest key for resource content revalidation; strict-vs-soft reflection as a server/operator policy rather than a client-negotiated knob.
|
Tightened this up to be opinionated — the Open Questions section is gone, replaced by a settled Design Decisions recap. The three items I'd previously left dangling are now firm:
No residual open questions; happy to argue any of these if reviewers disagree. |
TTL keeps a client from staying stale (proactive, time-bounded); the per-primitive digest corrects a client that is stale right now (reactive, deterministic at call time). Make this complementarity explicit in the Abstract and TTL interaction.
Hand edits from the author: tighten the abstract and motivation, reframe TTL as the pessimistic check and the digest as the optimistic check, add the deployment/connection-draining argument up front, broaden the other-drivers section (permissions, feature flags), and trim the aggregate/push discussion.
Summary
Early-stage proposal (SEP format) to sound out with the Transports WG before pursuing a core SEP.
Adds a deterministic, opaque surface digest carried on the
_metaof every server result — a content hash of the caller-visible primitive surface (tools, prompts, resources, resource templates). It gives stateless clients a deterministic, pull-based way to detect that a server's surface changed on any subsequent request, without an SSE stream,subscriptions/listen, or session IDs.Builds on the stateless/sessionless direction of SEP-2575 / SEP-2567, complements the TTL caching model of SEP-2549 (validator + budget), and follows the
_metaconventions of SEP-414.Why this fits the Transports WG
This is squarely a transport-layer change-detection concern: how does a client learn the server's surface changed (deploy, permission change, schema drift) when the transport is stateless and may be load-balanced across instances?
Motivated by concrete operational pain running a large remote MCP server:
tools/list_changedisn't universally honored (and stateless clients won't opensubscriptions/listeneither);list_changedto the right connection in a scaled fleet needs cross-instance fan-out and has no single 'change instant' during a rolling deploy.A per-response digest needs zero cross-instance coordination: each instance stamps the surface it serves; the client compares.
Key points
io.modelcontextprotocol/surfaceDigeston every result; optional per-kindsurfaceComponents.expectedSurfaceDigeston a request; servers MAY reject a staletools/callbefore executing with a newSurfaceChangedError(-32005, HTTP-412-style). Handles output/input-schema drift and lets the harness re-plan/re-prompt.Status
proposals/XXXX-deterministic-primitive-surface-digest.md.Feedback welcome on capability shape, whether to mandate RFC 8785 canonicalization, reflection scope, and deploy-time retry guidance (see Open Questions).