Preview. MCP Events is a draft MCP extension (experimental-ext-triggers-events). AI apps that support it use its webhook profile. The official MCP SDK does not implement it yet; @allternit/apps-server adds it on top, using the shared @allternit/mcp-events package.
With events, a host subscribes to a named event once, and your server POSTs each occurrence to the host’s callback URL. The host does not have to poll a tool.

Declare events

What the SDK does for you

  • Advertises the events capability in server/discover and initialize.
  • Serves events/list (definitions in name order), events/subscribe and events/unsubscribe to both protocol eras.
  • Validates the delivery: mode must be webhook, the callback URL must be https:// with no credentials and no localhost, private, link-local or CGNAT address literal, and subscribe needs a whsec_ secret of 24–64 bytes.
  • Derives a deterministic subscription id from (principal, url, name, arguments) (argument key order does not matter), so a repeated subscribe is idempotent.
  • Maps errors to the extension codes: -32011 NotFound (unknown event), -32012 Forbidden, -32013 ResourceExhausted, -32014 Unsupported (non-webhook delivery), -32015 CallbackEndpointError. A malformed callback or secret is -32602.
The principal defaults to the authenticated client id (ctx.http.authInfo.clientId), or anonymous. Pass principal(ctx) to derive it yourself.

Delivery format

Each POST carries Standard Webhooks headers signed with the subscriber’s secret, plus X-MCP-Subscription-Id:
The body is an event envelope { eventId, name, timestamp, data, cursor } or a control envelope: { type: "verification", challenge } (the receiver echoes { challenge }), { type: "gap", cursor }, or { type: "terminated", subscriptionId, error }. deliverWebhook re-checks the URL literally but does not resolve DNS. A server that delivers to user-supplied URLs must also refuse private addresses at connect time, and owns its own retry policy.

Receiving events

@allternit/mcp-events also verifies deliveries, for a receiver written in TypeScript:
Signatures older or newer than five minutes are rejected, and a header with several space-separated signatures (key rotation) passes if any matches. The package is tested against the Standard Webhooks reference vector and mirrors the Rust mcp/protocol crate.