Skip to content

Demo: receive MCP Events with Event Gateway as a forward proxy - #23

Merged
leggetter merged 17 commits into
mainfrom
feat/mcp-events-receive
Oct 14, 2026
Merged

leggetter merged 17 commits into
mainfrom
feat/mcp-events-receive

Conversation

@leggetter

@leggetter leggetter commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

A demo of Hookdeck Event Gateway acting as the forward proxy that MCP Events (SEP-3415, a draft MCP extension) describes for webhook delivery, in hookdeck/mcp-events-forward-proxy/. Verified against a dedicated Event Gateway test project, last on 2026-10-14 after Event Gateway's MCP Events handshake changes: 7 of 7 scenarios locally and 6 of 6 deployed.

  • Stand-in sender (src/sender/): an MCP server offering incident.created in webhook mode, standing in for any third-party MCP server.
  • Agent (src/agent/): an MCP client that owns its proxy. Per subscription it creates an MCP Events source with a fresh whsec_ secret and a connection (dedup on headers.webhook-id, retries), subscribes with the source URL, refreshes, and cleans up on exit.
  • Two delivery paths: locally through hookdeck listen (no public URL), and deployed over HTTP to the agent's endpoint, which checks x-hookdeck-signature.
  • Recovery when listen was down (src/agent/recover.ts): on start and on reconnect, the agent retries CLI_DISCONNECTED requests and settled FAILED events, and leaves anything queued or scheduled to Event Gateway. With --keep, a restarted agent picks up its existing source and subscription.
  • npm run scenarios: runs the scenarios and prints pass or fail with evidence: 7 locally (CLI), 6 deployed (HTTP). The deployed agent was last deployed on 2026-10-08; no later commit changes how the agent or sender behaves (only the MCP client's name).
  • A second sender: run unchanged (env vars only) against the Outpost demo's MCP server, with Outpost doing the delivery: filtering, a 503 retried by Event Gateway, and a clean listen stop recovered.
  • evdock spike: evdock's daemon receives through an MCP Events source and hookdeck listen with one small evdock change (a supplied secret); deliveries held over 5 minutes fail its timestamp check. Results in docs/PLAN.md (Phase 3).
  • Known limits stated in the README: an MCP client needs code to set up the proxy, the agent holds the project's API key, there's one source per subscription because a source holds one secret, nothing rejects a stale event (Event Gateway checks freshness on the challenge only), and the CLI path is for local development.
  • Also: a hookdeck/mcp-events-outpost stub linking the sending-side demo, and rows for both in the repo README.
  • Deployment: Fly.io config for the agent and sender. The apps are named mcp-events-receive-*, from the demo's earlier name; a comment in each fly.*.toml says so.
Verified results

First run 2026-10-08; the rows npm run scenarios covers re-run 2026-10-14. The rows marked by hand in the README weren't re-run.

Scenario Local (CLI) Deployed (HTTP)
Agent creates the source; the sender's challenge is answered by Event Gateway Yes Yes
Normal event Handled, attempt 1 Handled, attempt 1
Duplicate (same webhook-id, re-signed) Handled once; second request ignored by dedup Same
Badly signed delivery VERIFICATION_FAILED, never delivered; sender saw 200 Same
Agent answers 503, then recovers Retried every 15 s, then handled Handled on attempt 2; sender saw one 200
Forged request straight to the agent n/a 401
Refresh before refreshBefore Yes Yes, with deliveryStatus; one subscription kept for over 6 days
Ctrl+C Unsubscribes, stops listen, deletes source and connection n/a
listen stopped cleanly, then back CLI_DISCONNECTED; recovery retried the request; handled once n/a
listen killed, back after 30 s Retry rule delivered attempt 3; recovery left it alone; handled once n/a
listen killed until retries ran out Settled FAILED; recovery retried it; handled once n/a
Agent stopped with --keep, events sent, restarted Same source and subscription; both requests recovered; each handled once n/a

🤖 Generated with Claude Code

leggetter and others added 7 commits October 8, 2026 17:40
An MCP server (protocol 2026-07-28, MCP SDK v2) that offers incident.created in
webhook delivery mode, following OpenAI's MCP Events profile. It plays the part
of any third-party MCP server in the demo.

- events/list, events/subscribe (verification challenge before activation,
  TTL grants, dual-signing on secret rotation) and events/unsubscribe.
- Standard Webhooks signing on every attempt, bounded retries, no retry on
  410 or 413.
- npm run emit opens an incident, with options to send a duplicate or a
  badly signed delivery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… forward proxy

The agent owns its proxy: per subscription it creates an MCP Events source
holding a fresh whsec_ secret and a connection with a deduplicate rule on
headers.webhook-id and a retry rule, subscribes with the source URL,
refreshes before refreshBefore, and on exit unsubscribes and deletes both.

- CLI mode (default locally) delivers through hookdeck listen, supervised
  with its own CLI config under run/.
- HTTP mode (AGENT_PUBLIC_URL set) delivers to the agent's public endpoint,
  which checks x-hookdeck-signature.
- The endpoint re-checks the MCP server's signature without the 5-minute
  window, handles each eventId once and ignores verification bodies.
- npm run agent:status, agent:fail and teardown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm run scenarios checks the subscription, a delivery, a duplicate, a bad
signature, the agent down then back and, when deployed, a forged request,
and prints pass or fail with evidence. 6 of 6 passed against the deployed
services.

One image serves both services; fly.sender.toml and fly.agent.toml run them
as always-on single machines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/PLAN.md: the proposal, background on SEP-3415's forward proxy, who
  does what, status, verified results and remaining work.
- README.md and AGENTS.md for running and continuing the demo.
- hookdeck/mcp-events-outpost: a stub linking the sending-side demo.
- Repo README rows for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt and on reconnect

- recover.ts: retry CLI_DISCONNECTED requests scoped to the agent's
  connection (retry, not replay), retry events that settled as FAILED
  unless the agent answered 4xx, and leave queued or scheduled events to
  Event Gateway. Repeats every 60 s for up to 5 minutes.
- --keep leaves the subscription and endpoint in place on exit; the next
  start picks the endpoint up (secret read back with
  include=config.auth), re-subscribes and recovers.
- /demo/listen and npm run agent:listen stop, kill or start listen.
- Two CLI scenarios: listen stopped cleanly, and listen killed.
- Match the agent's resource names exactly (<prefix>-<8 hex>). The prefix
  match let a local agent delete the deployed agent's source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Correct the Phase 2 CLI cases from measurements with hookdeck-cli 3.1.0:
a crash doesn't hold events as pending, pausing doesn't cover a clean
stop, and recovery uses request retry. Record the 75 s hold as not
reproduced, and add the recovery results to the status table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
leggetter and others added 9 commits October 9, 2026 15:06
… server

The agent subscribed to order.created on the Outpost demo's server with
only env changes: filtering by Outpost, a 503 retried by Event Gateway,
and a clean listen stop recovered by request retry all behaved as with
the stand-in sender.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the WIP banner with a status line, and add how Event Gateway
covers SEP-3415's receiver duties, what's verified across local, deployed
and the Outpost demo as sender, what isn't (servers that follow SEP-3415's
capability location and error codes, rotation, CLI-mode refresh), and the
200 answer to a badly signed delivery as a known limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the server

The flowcharts ranked the MCP server between the agent and Event Gateway,
because the agent has edges to both. Sequence diagrams keep the declared
order (agent, proxy, server) and show the steps in time order. The README
steps now separate answering the challenge from storing and verifying
events, and say Event Gateway answers 200 once stored and verifies after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
evdock's daemon receives through an MCP Events source and hookdeck listen
with one small change (a supplied secret, --secret-env). Delivery, dedup,
bad signatures and refresh work; a delivery held over 5 minutes fails
evdock's timestamp check with 401, which the retry rule then repeats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…enge only

The evdock spike options assumed a proxy that checks freshness on
receipt. Event Gateway doesn't yet for deliveries, so say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The demo is Event Gateway in the role SEP-3415 calls a forward proxy, so
the directory now uses that term. The Fly app names are kept as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ness gap

Known limits now cover the code an MCP client needs to set up the proxy,
the project API key the agent holds, one source per subscription, and that
nothing rejects a stale event. The CLI path is described as local
development. Field report drafts stay out of the repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan now says the demo is done, with the deployed scenarios passing
again on 2026-10-14. The working group notes stay as notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…retry

The clean-stop scenario read the agent's recovery record as soon as the
event was handled. A recovery pass records its retries when it finishes,
and the retried event can be delivered first, so the check could fail on a
correct run. It now waits for the record.

Re-verified on 2026-10-14: 7 of 7 locally, including a fresh subscribe and
a refresh in CLI mode, and 6 of 6 deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leggetter
leggetter marked this pull request as ready for review October 14, 2026 15:49
…P note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leggetter
leggetter merged commit 9ca580c into main Oct 14, 2026
@leggetter
leggetter deleted the feat/mcp-events-receive branch October 14, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant