What MCP Events is
MCP Events lets an agent act on something the moment it happens, with nobody asking. A contact list is imported, a reviewer leaves a comment, an incident opens, and the agent hears about it from the server instead of finding out later.
MCP Events is a proposed extension to the Model Context Protocol, identified as io.modelcontextprotocol/events. It lets an MCP client subscribe to things that happen in the system behind a server, and receive each occurrence by polling, over a held-open stream, or as a signed webhook. The proposal is SEP-3415, written by Peter Alexander for the Triggers and Events Working Group, which Clare Liguori and Peter Alexander lead.
This guide covers why MCP needed events, how a subscription works from discovery to delivery, the 3 delivery modes and when each fits, how webhooks are secured, what is promised about ordering and loss, who supports it today, and how to add events to your own MCP server.
| Item | Status on 11 November 2026 |
|---|---|
| Proposal | SEP-3415, Extensions Track, status Draft |
| Opened | 5 October 2026; marked ready for review on 28 October |
| Protocol revision | Defined for 2026-07-28 and later |
| Reference implementation | Outstanding in an official SDK; conformance tests are open for review |
| Open questions | 5 listed in the proposal, including how events should reach the model |
Treat everything below as a draft that can still change. The shape has held steady since the working group’s design sketch in February 2026, but the error codes are provisional and nothing is merged.
Why MCP needed events
MCP is request-driven. A person or a model asks, and a server answers. That covers most of what agents do, and none of what happens while nobody is asking: a new email, a push to a repository, a contact list imported into a CRM.
MCP already has notifications, and the proposal explains why they are not enough:
- They carry no parameters.
notifications/resources/updatedsays a URI changed. It cannot say “a P1 incident was created for service X”, because there are no subscription arguments, no payload schema and no payload. - They need a live connection. A client that is not connected receives nothing, and a client that cannot hold a stream open has no way in.
- They cannot resume. There is no cursor, so whatever happened during a disconnect is gone, and the client cannot tell it missed anything.
The deployments that need events also pull in different directions. A serverless MCP server cannot hold a connection per subscriber. A client behind a firewall has no public URL for a callback. A client that pays per request should not poll. 1 delivery mechanism would strand at least 1 of them, which is why the proposal defines 3.
How MCP Events works
Take the CRM used across this site. Its MCP server already offers find_contacts, update_contacts and log_activity. With events it can also announce contacts.imported, so a sales team’s agent can update the contacts from every new trade-show list the moment it lands, with nobody typing the request.
- Discover. The server advertises the extension in the capabilities it returns from
server/discover. A client must not sendevents/*requests to a server that did not advertise it. - List.
events/listreturns each event type: a name, a description, the delivery modes it supports, aninputSchemafor subscription arguments and apayloadSchemafor what each event carries. - Subscribe. The client subscribes with a name and arguments, in a mode the event type offers. Arguments can filter the stream, such as 1 folder or 1 team, or shape the payload.
- Receive. Each occurrence arrives as
{eventId, name, timestamp, data, cursor}. The cursor is the subscription’s position, so the client can resume from it. - Act. The client decides what the agent does with it: add it to a conversation, start a new run, or queue it. The proposal leaves this to the application.
An event type, as events/list returns it
{
"resultType": "complete",
"events": [{
"name": "contacts.imported",
"description": "Fires when a contact list is imported into the CRM",
"delivery": ["webhook", "poll"],
"inputSchema": {
"type": "object",
"properties": { "teamId": { "type": "string" } }
},
"payloadSchema": {
"type": "object",
"properties": {
"importId": { "type": "string" },
"teamId": { "type": "string" },
"importedBy": { "type": "string" }
}
}
}]
}
The payload names the import and stops. The agent fetches the contacts through a tool when it needs them. The proposal asks for exactly this, under the name payload minimality: enough to identify and triage the event, never the full content.
A server that adds or changes event types at runtime can declare listChanged: true and send notifications/events/list_changed, the same pattern tools and resources use. Schemas should only grow. A breaking change ships under a new event name, alongside the old one for a while.
The 3 delivery modes
Each event type advertises any non-empty mix of poll, push and webhook. None is mandatory. The client SDK chooses a mode and runs the loop; the model only sees the events that arrive. Pick a mode to see the messages it takes:
events/pollname, arguments,cursor: nullresultno events, a fresh cursor,nextPollMs: 30000events/pollthe stored cursor, after 30 secondsresult1contacts.importedevent, a new cursor,hasMore: falseevents/streamname, arguments, cursor; the request stays opennotifications/events/eventcontacts.imported, sent on the open streamnotifications/events/eventthe next import, on the same streamcancelclosing the request ends the subscriptionevents/subscribecallback URL, awhsec_secret,ttlMsPOST challengea single-use nonce the endpoint must echoresultsubscription ID,refreshBefore, a cursorPOST eventcontacts.imported, signed with the secretevents/subscribethe same call again, beforerefreshBefore
- Server holds
- The subscription, its URL, secret and expiry
- Client needs
- A public HTTPS endpoint
- Fits
- Remote servers where a held connection is impractical
| Mode | Method | Subscription ends when | Fits |
|---|---|---|---|
| Poll | events/poll | The client stops polling. The server holds no state the protocol requires | Clients behind a firewall; stateless and serverless servers |
| Push | events/stream | The client closes or cancels the request | Local servers over stdio and clients that already hold connections |
| Webhook | events/subscribe | The TTL runs out without a refresh, or events/unsubscribe | Remote servers and hosted agents with a public endpoint |
Poll respects the server’s pace: nextPollMs says when to come back, and hasMore: true says to come back now and drain the backlog. Push carries the subscription ID in each notification’s _meta so a client with several streams can tell them apart.
Webhook delivery in detail
Webhook is the mode hosted agents will lean on, and the one with the most moving parts, because the server makes outbound requests to a URL a client supplied.
Subscribing to contact imports by webhook
{
"jsonrpc": "2.0",
"method": "events/subscribe",
"id": 2,
"params": {
"name": "contacts.imported",
"arguments": { "teamId": "sales" },
"delivery": {
"mode": "webhook",
"url": "https://agent.example.com/hooks/crm",
"secret": "whsec_<base64 of 24 to 64 random bytes>"
},
"cursor": null,
"ttlMs": 3600000
}
}
- The client owns the secret. It is required, it uses the Standard Webhooks format (
whsec_plus 24 to 64 random bytes in base64), and the server never generates one. Each subscription gets its own. - The endpoint proves it wants the traffic. Before delivery starts, the server sends a signed request with a single-use challenge, and the endpoint must echo it. This stops anyone from pointing a server’s events at a victim’s URL.
- The subscription expires unless refreshed. The client suggests a TTL; the server grants one and returns
refreshBefore. Callingevents/subscribeagain with the same name, arguments and URL refreshes it.ttlMs: nullasks for no expiry, which a server may grant. - Every delivery is signed. Each POST carries
webhook-id,webhook-timestampandwebhook-signatureheaders. The receiver checks the HMAC, rejects timestamps older than 5 minutes, and drops repeated IDs. - The server guards its own network. Callback URLs must be HTTPS. The server validates the IP address at delivery time, connects to that address, and follows no redirects, so a subscription cannot steer it at internal hosts.
The subscription ID is derived from the principal, the URL, the event name and the arguments, so subscribing twice returns the same subscription. It is a routing handle, not a credential: unsubscribing or rotating a secret still needs the authenticated principal.
Cursors, ordering and what is guaranteed
The proposal promises less than a message queue, on purpose, and says so plainly. What a client gets:
- Resume from a cursor. The cursor is opaque and server-defined. Replay is optional per event type; a type without it returns
cursor: null, and anything that happened during a disconnect is lost. - A flag for gaps.
truncated: truesays the server skipped events, for example because the cursor was older than its history. - Order within 1 subscription. Poll and push deliver in the order the server produces. Webhook order is best effort, because each event is retried on its own.
- At-least-once, with work. Delivery is at-least-once when the cursor is backed by a durable source and the client replays from it. Exactly-once is the client’s job, by de-duplicating on
eventId.
For the contacts agent this means 2 rules. The update must be safe to run twice on the same importId, because the same import can arrive twice. And the agent should read the contacts’ current state through a tool before acting, because the event says what happened, not what is true now.
Who supports MCP Events today
| Client | Support | Source |
|---|---|---|
| ChatGPT | Webhook mode only, in Work chats on the web, in the desktop app with Cloud selected, and in dots. 1 event per POST, body up to 256 KiB | OpenAI’s developer docs |
| Claude | Not supported. Claude’s connector docs list draft capabilities among the MCP features it does not support yet | Anthropic’s connector docs |
| Official MCP SDKs | No reference implementation yet; the proposal lists it as outstanding | SEP-3415 |
| Vercel AI SDK, TanStack AI, Mastra | Experimental support | Hookdeck’s guide |
ChatGPT’s implementation is the one to build against now. It needs events/list, events/subscribe and events/unsubscribe on the same authenticated endpoint as your tools. A person describes what to watch and what to do, for example watching a feedback channel for bug reports and opening draft pull requests, and ChatGPT subscribes for them. It may batch separately delivered events into 1 task run.
Security: an event is data, not an instruction
An event carries text from the outside world straight into an agent’s context, and it can start work with nobody watching. The proposal’s security section is built around those 2 facts.
- Payloads are untrusted. An email subject that says “ignore all previous instructions” is still just a subject. Clients should treat payloads the way they treat tool results, and servers should keep payloads small to shrink what can be injected.
- Authorisation happens 3 times. At subscribe time, the server checks the principal may watch that event with those arguments. At delivery time, it re-checks and ends the subscription if access was revoked. At action time, the agent’s response goes through normal MCP authorisation: receiving an event grants nothing.
- Policy sits between receipt and action. The governance section asks clients to support policy evaluation between an event arriving and the agent acting on it. It leaves the policy language to the application.
- Every mode has a kill switch. Stop polling, cancel the stream, or unsubscribe the webhook. A subscription granted no expiry has to be unsubscribed explicitly.
OpenAI adds 1 more from running it: watch for feedback loops. If the agent’s action changes data in the source app, and that change fires another event, the agent can run forever on its own output. Test for it before launch.
How to add events to your MCP server
The draft is not merged, so build where it is cheap to change: behind a flag, with the event logic separate from the delivery code.
Choose the events
- Start from tasks people already ask for. If agents call
update_contactsright after every import,contacts.importedis the event. Name events asnoun.past-tense-verb, the way the proposal’s examples do. - Make arguments filters. A list, a team, a severity. A subscription to every import in the account is rarely what anyone wanted.
- Send IDs, not content. Put the ID and the triage fields in the payload, and keep a read tool for the rest.
- Reuse upstream IDs. When the source has a stable ID, use it as
eventId, so the same import seen by 2 paths de-duplicates.
Make the tools safe to trigger
- Idempotent writes. Updating the same 9 contacts twice should log 9 activities, not 18.
- No echoes. Contacts the agent writes back must not fire
contacts.importedagain. Mark agent-made changes, or filter them out of the event. - Honest results. An update that changed 3 of 9 contacts should say so. With no person in the run, the tool result is the only thing that can.
Test the lifecycle
OpenAI’s test list is a good checklist for any client: advertise events, confirm events/list, subscribe, pass the challenge, trigger a matching event and a non-matching one, unsubscribe. Then test the edges: repeated subscribes, expiry and refresh across a restart, a disconnected account, revoked access, a bad signature, a duplicate delivery and a burst.
What changes when a task starts with no one watching
Today the contacts task ends at turn 9, when the person asks “Where are the rest? Can you try that again?” That question is how a team finds out that 6 of the 9 emails didn’t match. An event-started run has no turn 9. The agent updates 3 contacts, logs 3 activities, and nobody is there to ask.
- The run needs a starter. Record whether a person, a schedule or an event started it, and which event. Without it, an event storm looks like a surge in demand.
- Person-side signals disappear. Rephrasing, correcting and giving up need a person. What is left is the agent’s side: a retried tool, a changed argument, a partial update, a step cap reached.
- Cost has no one to notice it. A feedback loop is spend with no conversation attached. A limit per event type, on runs per hour and cost per run, belongs next to the policy check.
That is where Abracadabra fits. It reads each run as a task, with what started it, where it stopped and what it cost, so an event-started update that changed 3 of 9 contacts shows as a stopped task, not a quiet success.
What is still open
- How events reach the model. As injected context, a special message type, or a callback like a tool call. The proposal calls it an SDK question and leaves it open.
- Whether old notifications become events. Resource subscriptions and the
list_changedfamily could be re-expressed as events, gaining cursors and poll. - 1 subscription, several event names. Some sources yield 1 ordered feed of mixed types. Supporting that would change the wire format.
- Task updates as events. A reserved
mcp.task.updatedevent would let a client that started a long task and disconnected hear when it finished. - The error codes. 5 new codes are requested, and may be folded into
InvalidParamsinstead.
Delivery infrastructure, the signing, retries and replay, is real work. If your server already sends ordinary webhooks, keep them, and add events/list when a client you care about asks for it.
Read the runs nobody asked for
Events add a new kind of run to every agent: 1 that starts on its own and ends without a reply to anyone. Those runs need the same reading as a conversation, with the starter in place of the person.
Abracadabra groups the calls of each run into a task and marks where it stopped, whether a person or an event started it. It starts free; see pricing.
MCP Events FAQ
What are MCP Events?
MCP Events is a proposed extension to the Model Context Protocol, SEP-3415, that lets an MCP client subscribe to things happening in the system behind a server and receive them by polling, a held-open stream or a signed webhook, so an agent can react without a person asking.
Is MCP Events part of the MCP specification?
Not yet. It is a draft on the Extensions Track, opened on 5 October 2026 and marked ready for review on 28 October. Its error codes are provisional and an official SDK reference implementation is still outstanding.
Which delivery mode should my MCP server support?
Webhook if you want ChatGPT today, since it supports only that mode. Poll if your server is stateless or your clients sit behind firewalls. Push if your server runs locally over stdio. A server can offer any mix per event type.
Does Claude support MCP Events?
Not as of November 2026. Claude’s connector documentation lists draft capabilities among the MCP features it does not support yet. ChatGPT supports the webhook mode in Work chats, the desktop app with Cloud selected, and dots.
How are MCP Events webhooks secured?
Each subscription has its own client-supplied Standard Webhooks secret. The server verifies the endpoint with a single-use challenge, signs every delivery, sends only to HTTPS URLs it has validated, and follows no redirects. Receivers reject timestamps older than 5 minutes.