Commit graph experimental-ext-triggers-e.../docs/design-sketch-proposal.md
Author SHA1 Message Date
Peter Alexander
28ec35e905
Add schema-evolution guidance and termination on event-type removal or breaking change
A subscription (especially a no-expiry webhook one) can outlive the
events/list response it was created from, and nothing on the wire told the
client when its contract went stale. This adds:
- a Schema evolution note: inputSchema/payloadSchema SHOULD evolve additively
  for the lifetime of an event name; breaking changes go under a new name;
- an Event Type Removal and Breaking Changes subsection: the server SHOULD end
  affected push/webhook subscriptions via the existing terminated signal, with
  NotFound{kind:event} for removal and Unsupported{reason:schema_changed} for
  in-place change (not Forbidden), and an honest note that poll has no
  server-side subscription to terminate;
- list_changed now explicitly covers descriptor changes, and no-expiry clients
  are pointed at an occasional events/list alongside their refresh;
- a NOT-in-v1 bullet recording that schemaVersion/negotiation and an in-band
  schema fingerprint are deferred.

Also rewords the Streamable HTTP push bullet so it no longer refers to the
transport's GET SSE stream, which SEP-2575 removed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nj99r48USCY7S4xKoHD1Df
2026-09-04 16:33:00 +00:00
Peter Alexander
1590d923f7
Reconcile new guidance with surrounding text
Follow-ups to keep the new additions consistent with the rest of the doc:
- Recommended-grants paragraph no longer implies a server can grant no expiry
  unprompted; it points long-lived needs at an explicit ttlMs: null request and
  notes the range is a default, not a ceiling.
- The throttled prose drops 'coalesced' and routes outright event-skipping to
  the gap signal instead of conflating it with throttling.
- The well-known verification path states the result is still recorded per
  (principal, url); only the document fetch is cached per origin.
- The bounded retry window pauses while delivery is suspended, matching the
  resume-pending-events behaviour on refresh.
- The flow-control note lists the throttled/retryAfterMs visibility hint.
- The secret-generation decision row says 'endpoint verification' instead of
  naming only the challenge; one leftover 'param' becomes 'argument'.
2026-06-15 12:52:54 +01:00
Peter Alexander
baef671982
Add receiver-published well-known document as a fourth endpoint verification path
A callback URL's origin can serve /.well-known/mcp-webhook-receiver.json
listing the path prefixes that accept MCP webhook deliveries; control of the
origin stands in as proof of intent, acting as a standing self-serve allowlist
for gateways and integration platforms that can publish same-origin content.
The document is fetched over the same SSRF-hardened path as deliveries and may
be cached per origin. The threat model and decisions tables now list all four
verification paths. delivery.url remains required on every subscription.
2026-06-15 12:43:10 +01:00
Peter Alexander
6967f2ba30
Add recommended TTL grant range for webhook subscriptions
Adds guidance that finite grants of a few minutes up to about a day cover
most deployments, framed by the asymmetric costs (refresh traffic scales as
O(1/TTL), storage and reclamation as O(TTL)), with no-expiry as the right
choice beyond a day and minutes-to-an-hour caps for short-lived subscribers.
2026-06-15 12:41:43 +01:00
Peter Alexander
21be9c31c9
Surface active rate-limiting via deliveryStatus throttled and retryAfterMs
deliveryStatus previously only signalled failure-driven suspension, so a
client could not tell server-side throttling apart from a quiet stream or a
broken endpoint. Refresh responses can now carry throttled: true with an
advisory retryAfterMs hint; gap envelopes still cover events skipped outright.
2026-06-15 12:41:01 +01:00
Peter Alexander
b8f75563af
Add recommended thresholds for suspending failing webhook endpoints
The suspension threshold stays server-defined, but the doc now recommends
basing it on a sustained failure rate over a meaningful sample (illustrative:
above 95% failures over a rolling 60-minute window with at least 100
attempts) so brief blips and low-traffic subscriptions are not suspended
prematurely.
2026-06-15 12:40:06 +01:00
Peter Alexander
b506e34788
Add acknowledgment timeout guidance for webhook deliveries
States that the wait for a 2xx is server-defined rather than a protocol
parameter, with ~5 seconds noted as a common choice, and that endpoints
should durably accept the event, ack quickly, and process asynchronously
instead of holding the delivery request open.
2026-06-15 12:39:39 +01:00
Peter Alexander
905ade36a2
Bound webhook retries and add a per-delivery no-retry signal
Webhook retries were open-ended exponential backoff. Servers should now cap
both attempt count and elapsed retry window (illustrative: 3-5 attempts within
10-15 minutes); exhausted events are abandoned for watermark purposes and
recoverable via cursor replay when the upstream is durable. A receiver can
respond 410 Gone to suppress retries for a delivery it deliberately rejects,
treated like 413 as non-retryable without affecting the subscription.
2026-06-15 12:39:12 +01:00
Peter Alexander
2d798bfeab
Clarify that an absent cursor is equivalent to cursor: null
Cursor Lifecycle now states that omitting the cursor field is identical to
sending an explicit null in requests, responses, and delivered payloads, and
that neither side may fail on omission. This lets emit-only servers never
emit the field and lets clients with nothing persisted omit it.
2026-06-15 12:38:01 +01:00
Peter Alexander
082166f0ba
Rename inner subscription params field to arguments
The subscribe/poll/stream requests nested a params object inside JSON-RPC
params, which read as params.params in code and docs. The inner field is now
arguments, matching the inputSchema/arguments pairing on tools/call. Applies
to the wire examples, the subscription identity tuple, and the SDK guidance
snippets; the outer JSON-RPC params envelope is unchanged.
2026-06-15 12:37:28 +01:00
Peter Alexander
2f47edf7e2
Allow prior out-of-band verification as a third endpoint verification path 2026-06-12 19:41:27 +01:00
Peter Alexander
99f3589c83
Make webhook TTL client-suggested with explicit no-expiry option
The client now suggests a subscription lifetime via ttlMs on
events/subscribe; the server's refreshBefore grant is authoritative and
SHOULD only shorten (clamping up to a server minimum TTL is the one
sanctioned exception). An explicit ttlMs: null requests no expiry, and
refreshBefore: null grants it.

This reframes server-side durability as the server's choice, coupled to
the TTLs it grants: short grants keep the existing in-memory soft-state
model, while long or no-expiry grants shift durability and cleanup onto
the server (persist across restarts, persist verification status,
failure-based GC instead of TTL expiry, no expectation of unsubscribe).
Rationale: long-lived tenants with many subscriptions shouldn't be
forced into a server-invented refresh cadence, and a one-year TTL is
practically unlimited anyway — better to offer no expiry explicitly and
attach the real obligations to it.
2026-06-11 16:57:32 +01:00
Peter Alexander
1034fbbc52
Merge delivery-modes rationale from origin into design-sketch 2026-05-22 13:47:55 +01:00
Peter Alexander
567be29eb9
Consolidate event error codes into reusable general-purpose set
Replace the seven event-specific error codes (-32011..-32017) with five
general-purpose codes that carry their specifics in a typed `data` payload:

  NotFound, Forbidden, ResourceExhausted, Unsupported, CallbackEndpointError

Bad or non-https callback URLs and malformed secrets now fold into the
standard InvalidParams (-32602). The remaining codes are contiguous
(-32011..-32015) and named for reuse across the protocol rather than scoped
to events; clients still distinguish every case they act on by code, by
method, or by a typed `data` discriminator.

Also corrects the stale -32002 ResourceNotFound reference (no longer present
in the schema) and records the trade-off in Key Design Decisions.
2026-05-22 13:45:50 +01:00
Peter Alexander
9f9cdce8b1
Resolve endpoint verification and add Security threat model
- Replace Open Question 6 with normative endpoint-verification rules:
  a mandatory intent challenge (or allowlist) before activating webhook
  delivery, cached per (principal, url); add -32014 EndpointVerificationFailed
  and fill the verification control-envelope row.
- Add optional asymmetric server signing (Standard Webhooks v1a,) for
  known-server allowlists. The webhook-signing key is discovered from an
  origin the client already authenticates: inline webhooks.signingKeys on
  the SEP-2127 server card if it lands first, otherwise a standalone
  /.well-known/mcp-webhook-jwks.json document (one chosen at SEP-review time).
- Correct the webhook-secret rationale (HMAC stops deception, not flooding)
  and add Key Design Decisions rows for endpoint verification and server identity.
- Add a Threat Model summary table to Security Considerations.
2026-05-22 13:07:23 +01:00
Peter Alexander
754be6ce47
Add rationale for three delivery modes to Key Design Decisions
Records why poll, push, and webhook all exist as protocol-level modes
rather than picking one: the deployment topologies are disjoint and no
mode subsumes another. Acknowledges the departure from the usual
one-way-to-do-it stance and the spec/SDK cost it carries, and notes the
rejected alternative (one mode + proxy synthesis) and why.
2026-05-14 18:36:25 +01:00
Peter Alexander
197c32b4c5
Align push correlation with SEP-2575 and rename duration fields to Ms suffix
- Push notifications now carry the parent events/stream request id in params._meta["io.modelcontextprotocol/subscriptionId"] (per SEP-2575) instead of params.requestId
- Rename nextPollSeconds -> nextPollMs and maxAge -> maxAgeMs (integer milliseconds) to match the MCP duration-field convention; example values and prose updated
2026-05-10 16:45:42 +01:00
Peter Alexander
d4faef9758
Add nextCursor to events/list and _meta to event types and occurrences
events/list response now shows nextCursor for consistency with base MCP list pagination. Event-type definitions and EventOccurrence both gain optional _meta, matching Tool/Resource/Prompt.
2026-05-01 20:15:53 +01:00
Peter Alexander
8c6a1343cc
Redact example secret to avoid triggering secret scanners 2026-04-30 23:11:37 +01:00
Peter Alexander
58df0af65b
Revise Events design sketch: gap model, replay bounding, identity hardening, decisions table
- Unify cursor expiry into in-band `truncated: true` (drop -32014 CursorExpired): poll result body, push re-sends notifications/events/active mid-stream, webhook refresh response + {"type":"gap"} control envelope; client never re-subscribes for a gap
- Add `maxAge` (seconds) to bound replay across all modes; servers may decline by truncating to now
- Heartbeat carries cursor (push) and refresh response carries the safe-watermark cursor (webhook) so the client's position advances during quiet periods
- Webhook now requires an authenticated principal (key is (principal, url, name, params)); drop body `id` (route via X-MCP-Subscription-Id); add Non-event webhook bodies subsection (gap/terminated/verification with `type` discriminator)
- Drop subscription `id` from poll/stream (one sub per request; correlate via JSON-RPC id / requestId)
- Rewrite Summary as a how-it-works overview; add Key Design Decisions table; add OQ5 (task state changes as events) and OQ6 (endpoint verification + asymmetric server signing)
- Consistency pass across diagrams, examples, error lists, and appendix step references
2026-04-30 23:02:10 +01:00
Peter Alexander
26ba242ff0
Revise Events design sketch: Standard Webhooks, client-supplied secret, tuple-based identity
- Adopt Standard Webhooks as the delivery signature scheme (webhook-id/timestamp/signature, v1 base64, multi-sig rotation); keep X-MCP-Subscription-Id as the one MCP-specific header
- Webhook secret is now client-supplied and REQUIRED (whsec_ + 24-64B base64, server MUST reject otherwise); server never generates one
- Subscription identity is (principal?, delivery.url, name, params); the response returns a server-derived id used only for routing, never as input
- Revert to no-mode-mandatory for delivery modes
- TLS is now MUST for delivery.url
- Allow cursor: null in any delivery for event types without replay; clarify CursorExpired always implies a possible gap
2026-04-30 15:02:32 +01:00
Peter Alexander
249efdcc63
Revise Events design sketch: poll mandatory, webhook delivery profile, SEP review fixes
Make poll a mandatory delivery mode so every events-capable server is reachable by every client (push/webhook remain optional optimizations). Add a Webhook Delivery Profile subsection with WAF-relevant invariants (POST-only, required headers, 256 KiB body SHOULD, TLS termination) and defer User-Agent to SEP-1336 and egress-IP discovery to SEP-2127. Incorporate further fixes from adversarial review.
2026-04-30 14:30:53 +01:00
Peter Alexander
641eca02a7
Revise Events design sketch: webhook cursor model, subscription identity, security hardening
Resolve internal contradictions and design gaps found in review:
- Webhook cursors are now client-owned (per-event retry, no server watermark); name/params/delivery.url are immutable identity fields; unsubscribe requires delivery.url
- Unify error/terminated shape across modes; define heartbeat and terminated notifications; renumber error codes to -32011..-32016 to avoid base-spec collision
- SSRF: validate at delivery time, block fe80::/10, MUST NOT follow redirects; HMAC: X-MCP-Subscription-Id header, hex-lowercase encoding, mandatory timestamp, retry regenerates
- events/stream scoped to event notifications only (existing GET SSE retained); StreamEventsResult send rules clarified per transport
- Broadcast emit now uses author-supplied match/transform hooks with ctx; poll lease keyed on (principal, eventName, hash(params)); eventId SHOULD be upstream's stable identifier
- Qualify at-least-once for emit-only types; refresh reactivates suspended delivery; capability uses listChanged
- Add Open Questions on multi-event-name subscriptions and ownership-verification handshake

Add design-sketch-revision-deltas.md documenting all changes for implementations built against the prior revision.
2026-04-17 11:52:12 +01:00
Peter Alexander
4f8a1129d8
Add delivery-mode diagrams, e2e webhook appendix, and clarifications
- Mermaid sequence diagrams for poll/push/webhook delivery
- Appendix: end-to-end GitHub example with Redis sub store
- Push: clarify multiple concurrent streams allowed; note HTTP/2 dependency
- Webhook: specify 2xx ACK requires durability or end-consumer processing
- Use nextPollSeconds consistently
2026-04-10 12:41:21 +01:00
Peter Alexander
603f5de12a
Add Events design sketch proposal 2026-04-09 21:30:11 +01:00