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.
- 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
- 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
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.
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.