MCP Events: build an event-emitting MCP server for ChatGPT

This research library uses AI-assisted source research and drafting. Linked sources support product claims; analysis and proposed exercises are our interpretation. Unless an article documents a test and its results, do not read it as a hands-on review or an independently verified benchmark.
MCP Events lets ChatGPT subscribe to updates from your MCP server, such as new messages, comments or status changes, and respond the way the user asked. OpenAI announced ChatGPT support for the proposed MCP Events specification at DevDay 2026 for all plans, so plugins can start automations when something happens in a connected app. The MCP Events guide describes exactly what your server must implement.
This article walks through those requirements in build order, then shows how to hand the work to Codex with a test plan it can check against.
What ChatGPT supports from the MCP Events specification
The specification is still a proposal. The MCP Triggers and Events Working Group was chartered in March 2026, and its first SEP for events is listed as ideating. ChatGPT implements part of the draft today.
| Area | Status in ChatGPT |
|---|---|
| Protocol version | MCP 2.0, protocol version 2026-07-28 required |
| Webhook delivery | Supported |
| Callback verification | Supported |
| Polling and streaming delivery | Not supported |
| Gap and terminated notifications | Not supported |
| Server requirements | Persistent subscription storage, outbound HTTPS to callback URLs |
Because the spec can change, keep your event code behind one module so a later revision touches one place.
How MCP events move from your server to ChatGPT
The flow has five steps. Your server lists the events it supports. The user tells ChatGPT what to monitor and how to respond. ChatGPT subscribes through your MCP server and supplies a callback URL and a signing secret. Your server sends matching events to that URL. ChatGPT receives each event in the subscribed chat and follows the user's instructions.
The docs give two example uses. One monitors a product feedback channel for bug reports and opens draft pull requests with fixes and tests, using message.created filtered by channel_id. The other watches a document for review comments, using comment.created filtered by document_id.
Advertise events and define them
Discovery starts in the capabilities your server returns from server/discover. Add an events entry next to tools:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"events": {}
}
}
}
Then implement three methods on the same authenticated MCP endpoint as your tools. events/list describes available events and their filters. events/subscribe creates or refreshes a subscription. events/unsubscribe stops one.
Each event definition from events/list carries a name, a description, the supported delivery modes, an inputSchema for subscription arguments and a payloadSchema for the delivered data. Use stable names and specific descriptions. Expose filters such as a document, project or queue ID, and apply them on your server before delivery. Return only events the connected account is allowed to discover, and page with nextCursor if the catalog is long.
Handle events/subscribe without surprises
ChatGPT sends the event name, the filter arguments and a webhook destination:
{
"jsonrpc": "2.0",
"id": 2,
"method": "events/subscribe",
"params": {
"name": "comment.created",
"arguments": {
"document_id": "doc_123"
},
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/mcp-events/callback_123",
"secret": "whsec_<base64-encoded-signing-key>"
},
"cursor": null
}
}
Before you accept, check four things. The user must be authorized for the event and its arguments. The name and arguments must validate against your definition, and the secret must start with whsec_ and decode to 24 to 64 bytes. The callback URL must pass validation and verification. Finally, store the subscription with its owner, filters, URL, secret and expiration.
Derive a deterministic subscription ID from the authenticated principal, callback URL, event name and arguments. Compare arguments as canonical JSON so key order cannot create duplicates. Return the ID with refreshBefore, the expiration you grant, and return cursor: null for event types without replay.
ChatGPT refreshes a subscription by calling events/subscribe again before refreshBefore. Honour ttlMs when present, and return refreshBefore: null only when you grant a request for no expiration.
Verify the callback and sign each delivery
Before any application data, send a signed verification request with a fresh, single-use challenge. ChatGPT echoes the challenge. Require a 2xx response and compare the value in constant time. If verification fails, return JSON-RPC error -32015 with a reason such as challenge_failed or timeout.
Treat the callback URL as untrusted. Require HTTPS, resolve and validate the address at connection time, block private and local addresses, and do not follow redirects.
Deliveries use Standard Webhooks. Each request carries Content-Type, webhook-id equal to the event's eventId, webhook-timestamp in Unix seconds, webhook-signature, and X-MCP-Subscription-Id. The docs show a Node.js sender built on this package:
npm install standardwebhooks
Serialize the body once and send those exact bytes, because the signature covers them. Send one event per request, at most 256 KiB. Retry transient failures with exponential backoff and a bounded number of attempts, keep the event ID, and sign each attempt again. Do not retry a 410 or 413.
Events can arrive out of order, so make your write tools idempotent. Keep user-authored text inside data and never add instructions for the model to the payload.
Build and test it with Codex
This is a good task for Codex because the docs already contain the acceptance tests. Put the rules where Codex reads them, then let it work in small steps.
- Add the constraints to AGENTS.md: protocol version 2026-07-28, webhook only, no redirects, private addresses blocked, 256 KiB cap.
- Ask Codex to add the
eventscapability andevents/listfor one event, with unit tests for the definition. - Ask for
events/subscribeandevents/unsubscribewith deterministic IDs, canonical JSON comparison and persistent storage. - Ask for callback verification and a signed sender, then review the address checks yourself before merging.
- Connect the server through a plugin, rescan it, and confirm the events appear beside your tools on the plugin page.
Then run the documented lifecycle checks in ChatGPT. Verify that a subscription arrives with the expected arguments, that a matching event gets a 2xx, and that a non-matching event is not delivered. Also test refresh across a server restart, revoked access, invalid signatures, duplicate deliveries and bursts with batching on and off. If your automation writes back to the source app, check that its own changes do not trigger a feedback loop.
We use the same pattern in our Delegate, Review, Own methodology: the agent builds, and a person reviews against a written checklist before the change ships.
Pick one event your users already ask to be notified about, and implement only that one first.