MCP goes server-initiated: auditing the Events design sketch
An MCP server still cannot tap the agent on the shoulder. When the August roadmap made "agentic messaging primitives" its first priority, the concrete design work was already sitting in an incubation repo: a three-mode Events primitive — poll, push, webhook — that got its last substantive revision four days ago.
The trail runs through four documents. The MCP roadmap of 22 August 2026, by lead maintainers David Soria Parra and Den Delimarsky, names server-initiated events — "webhooks and channels" — under its first of five priority areas, and promises expedited review for SEPs that land inside them. The Triggers and Events Working Group, chartered 24 March 2026 and co-led by Clare Liguori (AWS) and Peter Alexander (Anthropic), owns the mechanism. The design itself is Alexander's Events design sketch, a draft dated 19 February that has been absorbing review since April and was amended again on 4 September. And a field report filed on 24 August supplies the thing standards discussions usually lack: thirty days of production data on what polling actually costs.
We read all four, plus the subscriptions/listen machinery that already shipped, the competing proposal that was closed unmerged six days ago, and the open spec bug that shows how hard server-initiated teardown is to get right. This is an audit of where MCP's push layer stands, one primitive at a time.
What request-response cannot express
MCP's contract has always pointed one way. The client calls tools; the server answers. The server can send notifications — notifications/tools/list_changed, notifications/resources/updated — but those update client-side metadata. They never start a new model turn. The still-open SEP-2495 states the gap plainly: the LLM cannot wait for user interactions, sensor data, or webhooks from the server side, so an agent that should react to a Slack message or a PagerDuty incident has to poll for it — and every poll that comes back empty is a wasted model turn.
The 2026-07-28 specification took the first step with subscriptions/listen: a long-lived request that opens a notification stream, replacing the old resources/subscribe RPC and the HTTP GET endpoint. The client sends a filter naming which notification types it wants; the server must acknowledge before delivering anything, must not send unrequested types, and tags every message with a subscription ID in _meta so concurrent streams demultiplex cleanly. It is a real subscription primitive with real ordering rules.
But look at what the filter can contain: toolsListChanged, promptsListChanged, resourcesListChanged, and a list of resource URIs. All four are protocol metadata. There is no way to say "tell me when a new email arrives" or "wake the agent when a P1 incident opens". subscriptions/listen solved delivery for the notifications MCP already had. It did not create a vocabulary for the events agents actually care about. That vocabulary is what the Events sketch adds.
Even the shipped machinery has unfinished edges. An issue opened on 6 September, #3348, documents that two pages of the 2026-07-28 revision disagree about how a server ends a subscriptions/listen stream: the cancellation page says the server MUST send notifications/cancelled, the subscriptions page says it SHOULD respond to the original request and close — and both official SDKs implement the second behavior on every transport. Server-initiated teardown is exactly the kind of semantics the Triggers and Events WG will have to nail down for Events, and the drift shows how easily it fractures.
The Events primitive: typed occurrences, discoverable like tools
The design sketch models events the way MCP models tools. A server declares its event types via events/list: each descriptor carries a name, a description, an inputSchema for subscription arguments (filters like from: "*@example.com" or severity: "P1"), a payloadSchema for the delivered data, and the delivery modes that event type supports. Dynamic catalogs reuse the familiar pattern: notifications/events/list_changed tells the client to re-list.
Every delivered occurrence has the same shape regardless of mode: eventId for deduplication (preferably the upstream system's stable identifier, so the same Stripe event surfaced by webhook and by poll backfill dedups correctly), name, timestamp, data conforming to the payload schema, and a cursor.
Cursors are the sketch's answer to reliability without mandating it. A cursor is an opaque server-defined position; the client persists the latest one and hands it back on the next poll, reconnect, or refresh, and the server resumes from there. Servers backed by a durable upstream get ordered replay. Servers with no addressable history return cursor: null and the client lives with at-most-once. A truncated: true flag in any response is the honest signal that events were skipped — a stale cursor, a maxAgeMs floor, or a server-side ceiling. Nothing pretends delivery is guaranteed when it is not, which is a more defensible position than the implicit exactly-once assumptions most ad-hoc webhook integrations make.
Three delivery modes, none mandatory
Poll is the floor. The client SDK — not the model — calls events/poll on a loop:
// request
{ "name": "email.received",
"arguments": { "from": "*@anthropic.com" },
"cursor": "historyId_99840", "maxEvents": 50 }
// response
{ "events": [ … ],
"cursor": "historyId_99842",
"truncated": false, "hasMore": false,
"nextPollMs": 30000 }
The server holds no protocol-required per-client state: every poll is self-contained, which makes this mode a natural fit for the stateless core the 2026-07-28 release standardized. nextPollMs lets the server steer the cadence, and clients apply a floor (default one second) so a misbehaving server cannot induce a tight loop. Crucially, the poll is a protocol-level operation. The LLM is invoked only when the events array is non-empty — the SDK absorbs the empty round trips that today burn model turns.
Push is a long-lived events/stream request, one per subscription, delivering notifications/events/event messages as they occur. The server must heartbeat at least every 30 seconds, and the heartbeat carries the current cursor, so a client's persisted position advances even through quiet hours. On HTTP/1.1 each stream costs a TCP connection, so multi-subscription clients effectively depend on HTTP/2 multiplexing; on stdio, streams share stdout and demultiplex by subscription ID — the same correlation convention subscriptions/listen uses.
Webhook is the mode the roadmap headline was about, and the sketch treats it with the most care, because it is the only mode where the server holds real state. The client calls events/subscribe with an https callback URL and a client-supplied signing secret — the literal whsec_ prefix plus base64 of 24 to 64 random bytes, which servers must reject if malformed. Every delivery is signed following the Standard Webhooks convention (webhook-id, webhook-timestamp, webhook-signature) plus an X-MCP-Subscription-Id routing header. Secret rotation is built in: supply a new secret on refresh and the server dual-signs through a grace window.
Subscription lifetime is a negotiation. The client suggests ttlMs; the server answers with refreshBefore, an authoritative expiry the client must refresh against. The grant should not exceed the suggestion — with one sanctioned exception, a server-side floor against refresh storms. A client can request no expiry with ttlMs: null, and the obligations invert: a server that grants it must persist the subscription across restarts, because a client that never refreshes can never detect a silently dropped one. The TTL is explicitly framed as the server's resource-control knob — short grants keep subscriptions as in-memory soft state that expiry garbage-collects; long grants shift the durability burden onto the server.
Identity is where the sketch is strictest. A webhook subscription is keyed on (principal, delivery.url, name, arguments) — no client-generated IDs. And the principal is not optional: events/subscribe requires an authenticated caller, because without a principal in the key the remaining tuple is guessable and any caller could unsubscribe or rotate another tenant's secret. An unauthenticated MCP server may offer poll and push. It may not offer webhooks. That single sentence quietly makes identity a prerequisite for the delivery mode most production deployments will want, which lands close to the roadmap's separate priority on agent identity.
The sketch also mints a general-purpose error family — -32011 NotFound, -32012 Forbidden, -32013 ResourceExhausted, -32014 Unsupported, -32015 CallbackEndpointError — designed as candidates for promotion into the base MCP error registry, with typed data payloads instead of new numbers per condition. Future SEPs are asked to reuse them. If that happens, an events extension will have incidentally standardized MCP's error taxonomy.
Thirty days of long-polling, measured
The field report in the incubation repo is the most useful document in the thread. An engineering team at Simetrik runs a remote MCP connector (Python, Streamable HTTP, OAuth) behind an AWS ALB and an nginx ingress, with tools that hand work to an agent running for minutes. Their workaround for the missing push layer is the one everybody uses: block inside tools/call for a bounded 45 seconds, return a handle, block again in a second tool.
Over 30 days and 74 successful calls: p50 45.9 seconds, p95 241.7, max 241.9. The long poll itself held — no server-side disconnect, ever, including at four minutes. The damage was elsewhere: 22 calls, 30 percent of the total, ran past the roughly 60-second ceiling the team observes on their target clients. Every one was logged as a success. The server finished the work, recorded ok, and had no way to know nobody was listening — or to deliver the result afterwards. Their success metric and the user's experience diverged silently, 22 times.
The report names the candidate show-stopper for a poll-only v1: a server needs some way to learn its result was never collected, or the handle must be durable enough that the answer survives the abandoned call. That is an argument for pairing Events with the Tasks extension — whose maturation the roadmap lists in the same priority area, and whose task-completion notifications the WG charter explicitly claims ownership of.
Governance: one proposal died so this one can move
The path is narrower than it was a month ago. SEP-1803, an Event Subscriptions proposal opened by an OpenAI engineer in November 2025, was closed unmerged on 2 September by lead maintainer David Soria Parra, after months of automated inactivity pings to its assigned sponsor went unanswered. Whatever the design's merits, the SEP process routed around a stalled sponsorship — and the WG's incubation repo is now the only active venue for this problem.
Inside that venue, the sketch is still moving. On 4 September the author pushed schema-evolution rules in response to review: descriptors should evolve additively for the lifetime of an event name, breaking changes should ship under a new name served alongside the old one, and live subscriptions to a removed type get terminated with a typed error rather than left to fail mysteriously. The WG's stated bar for success is an accepted SEP, reference implementations in at least two Tier-1 SDKs, and conformance coverage. Under the roadmap's expedited-review rule, this lands sooner than the absence of dates suggests.
What it means for LLM4Agents
We sit on both sides of this protocol. As an MCP server operator, our tools surface assumes the polling model the field report just priced: an agent that submits a long inference job to the gateway checks back for it, spending a turn per check. Events gives us the vocabulary to invert that — inference.completed, balance.low, settlement.confirmed as declared event types with payload schemas, delivered by poll for free-tier agents and by signed webhook for registered ones. Our platform already exposes webhooks for billing; the sketch tells us what shape those should converge on: Standard Webhooks signatures, client-supplied whsec_ secrets, dual-sign rotation, TTL-scoped subscriptions.
The identity coupling works in our favor. Webhook mode requires an authenticated principal, and the subscription key is scoped to it — which maps one-to-one onto gateway API keys and the agent identities behind them. An agent funded in stablecoins that registers a callback for balance.low is exercising the same principal it pays with. And the payload-schema discipline matters for payments specifically: a signed, schema-conformant settlement.confirmed event is machine-verifiable evidence an agent can act on without a human reading a dashboard.
The threat is symmetrical: if Events ships and we still require polling, every competitor whose gateway calls the agent back is cheaper per job than we are, in model turns if not in dollars.
Staying on the frontier
First, implement subscriptions/listen now. It is in the shipped 2026-07-28 specification, both Tier-1 SDKs support it, and it is the substrate Events-style delivery will ride on. Follow the SDK behavior on teardown — successful response, then close — which is where issue #3348 says the spec text will align.
Second, prototype Events poll mode behind a flag. Poll is stateless, self-contained, and matches our gateway's architecture; the surface is three methods and a cursor. Tracking the sketch now means our feedback arrives while the design is still soft, and migration later is a rename rather than a rewrite.
Third, converge our billing webhooks on the sketch's webhook contract. Standard Webhooks signing, whsec_ client-supplied secrets, X-MCP-Subscription-Id routing, TTL refresh. Every field of that contract we adopt early is migration work we do not do later.
Fourth, publish our own field report. The WG is voting on v1 mechanisms with n=74 from one deployment. Our gateway sees polling behavior across many agents; thirty days of our numbers on abandoned polls and wasted turns is cheap to produce and buys a seat in the room where the mechanism gets decided.
Fifth, adopt the -3201x error family in our MCP surface. The codes are explicitly designed for reuse and for promotion into the base registry. Being early costs nothing; being late means a breaking change.
Pay per inference, in stablecoins, over an OpenAI-compatible API
x402 and EIP-3009 settlement, model routing and fallback, no subscription.
Register an agent