Watch MCP Events
List a server's event types, poll one, hold a stream open, or point a webhook subscription at the inspector itself, with every delivery checked against what the extension requires.
MCP Events lets a server tell a client that something happened, three ways: the client polls, the server pushes on a held-open stream, or the server POSTs to a webhook the client names. Each is easy to get almost right, and the difference shows up as a client that misses an event or sees one twice.
The inspector takes all three from the client side. It speaks the extension on
the 2026-07-28 wire, against any server that offers it, not only mcpg. A
server without the extension lists no event types; that is an answer, not an
error.
Poll, stream, subscribe
mcpg inspector events list https://gw.example/mcp
mcpg inspector events poll https://gw.example/mcp orders.created --args '{"shop":"eu"}'
mcpg inspector events stream https://gw.example/mcp orders.created --seconds 60
mcpg inspector events subscribe https://gw.example/mcp orders.created \
--callback-base https://inspect.example.net --seconds 300
listprints every type with itsinputSchema,payloadSchemaand the delivery modes it offers.pollsends oneevents/polland prints the result: the events, thecursorto read on from, andnextPollMs.--cursorreads on from a saved one;--max-eventscaps the page.streamholdsevents/streamopen and prints each frame as it arrives, one JSON line each with--json. It ends at--seconds, at Ctrl-C, or when the server ends the stream, and then prints a report with the stream checks.subscribelistens for deliveries (--listen, default127.0.0.1:0), makes a webhook subscription whose callback is that listener, prints each delivery as it arrives, and unsubscribes when it ends. Its last document is the delivery count and the webhook checks.unsubscribeends a webhook subscription made elsewhere, by its callback URL:--url https://hooks.example.com/mcp/7f3a.
--args takes inline JSON or @file, and must be an object: subscription
arguments are what a type's inputSchema describes.
Reaching the inspector for a webhook
A webhook subscription only works when the server can reach the callback, and
many servers, mcpg among them, send only to https. The inspector makes the
callback under the address it listens at, so on a laptop that is
http://127.0.0.1:…, which a remote server cannot reach and an https-only
one refuses.
--callback-base names the address the server should use instead: a tunnel
or a TLS proxy that forwards to the listener. The callback path is
/hooks/<target>/<id>, with an id nobody can guess, so one public address
serves every subscription the inspector holds. A hosted inspector uses its
public URL by default.
Each callback has its own whsec_ secret, made for that subscription; a
recording redacts it. A delivery whose signature does not verify, or whose
timestamp is stale, is refused with 401 and counted, so a server signing
with the wrong secret shows up as refused deliveries rather than as silence.
What it checks
Every delivery and every stream frame is kept, and checked against what the extension requires of the sender.
| Check | Passes when |
|---|---|
events.webhook.signature | Every delivery is signed with the subscription's secret and is fresh |
events.webhook.id-is-event-id | An event's webhook-id is its eventId |
events.webhook.stable-retries | A retried event keeps its webhook-id |
events.webhook.control-ids | A control envelope's webhook-id starts with msg_ |
events.webhook.id-has-no-dot | No webhook-id contains a . |
events.webhook.cursor | Every event body carries a cursor (null for a type without replay) |
events.stream.active-first | The first frame is notifications/events/active |
events.stream.heartbeat | A data frame arrives at least every 30 s |
events.stream.cursor | Every event and heartbeat carries a cursor |
events.stream.well-formed | Every frame is a stream notification the extension defines, with its required fields |
events.webhook.id-has-no-dot checks a rule of
Standard Webhooks,
not of MCP Events: a sender must not put a . in a message id, because the
signed content is {webhook-id}.{webhook-timestamp}.{body}. A server that
uses an upstream id such as a Kafka topic with a dot as its eventId fails it
while it still follows MCP Events. The callback still takes such a delivery,
as the reference libraries do.
The verification challenge a server sends before its first delivery is
answered with its echo, and kept with the other deliveries, so a server that
skips verification is visible too.
In the web UI and the TUI
The events pane lists the types. Select one and its inputSchema becomes
the argument form, as a tool's does. Poll runs one poll and records it in
the history. Stream opens a stream that, like a subscription, outlives the
pane: leave for another tab and the tab counts what arrived. Webhook makes
a subscription whose callback is this inspector; its deliveries, with the
checks above, are under webhooks, each with an unsubscribe. A field
under the form sets the callback base for that subscription.
The TUI has an events screen: j/k selects a type, a edits the
arguments (f for the form), p polls once, and enter opens or closes a
stream on the selected type. Frames tail below the detail, and the tab counts
the ones that arrived while you were elsewhere.
Webhook frames also land in the target's wire log as channel webhook, next
to the requests that made the subscription.
Event types in a snapshot
snapshot records each event type with its schemas whole, and diff judges a
change by whether a subscriber survives it: a new optional argument or payload
field passes --mode compatible, a newly required argument does not. See
Gate CI on it.
Next steps
- MCP Events — serving events from the gateway.
mcpg inspectorreference — every verb and flag.