Inspector
Inspectorbeta

Watch MCP Events

beta
v0.1.0-beta.49

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

bash
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
  • list prints every type with its inputSchema, payloadSchema and the delivery modes it offers.
  • poll sends one events/poll and prints the result: the events, the cursor to read on from, and nextPollMs. --cursor reads on from a saved one; --max-events caps the page.
  • stream holds events/stream open 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.
  • subscribe listens for deliveries (--listen, default 127.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.
  • unsubscribe ends 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.

CheckPasses when
events.webhook.signatureEvery delivery is signed with the subscription's secret and is fresh
events.webhook.id-is-event-idAn event's webhook-id is its eventId
events.webhook.stable-retriesA retried event keeps its webhook-id
events.webhook.control-idsA control envelope's webhook-id starts with msg_
events.webhook.id-has-no-dotNo webhook-id contains a .
events.webhook.cursorEvery event body carries a cursor (null for a type without replay)
events.stream.active-firstThe first frame is notifications/events/active
events.stream.heartbeatA data frame arrives at least every 30 s
events.stream.cursorEvery event and heartbeat carries a cursor
events.stream.well-formedEvery 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