-
-
Notifications
You must be signed in to change notification settings - Fork 0
Requirements
liplus-lin-lay edited this page Mar 31, 2026
·
1 revision
Receive and persist GitHub webhook events via a Cloudflare Worker, and provide them to AI agents through the MCP protocol. This enables agents to detect GitHub state changes in real-time and respond autonomously.
- GitHub webhooks are delivered directly to a Cloudflare Worker (no local server required)
- Events are persisted in SQLite within a Durable Object
- AI agents connect via MCP stdio transport (local bridge) or Streamable HTTP (remote)
- The local bridge proxies tool calls to the Worker and relays real-time notifications via SSE
GitHub --POST--> Cloudflare Worker --> TenantRegistry DO
| Signature verification | installation_id -> account_id
| UUID assignment |
| v
| WebhookStore DO (SQLite) [per-tenant]
| |
+-- /mcp (Streamable HTTP) +-- SSE real-time stream
| WebhookMcpAgent DO +-- REST endpoints
| [per-tenant] /pending-status
| +-- tools -> WebhookStore /pending-events
| /webhook-events
+-- /events (SSE) /event
| +-- WebhookStore DO /mark-processed
|
+-- /webhooks/github (POST)
+-- TenantRegistry -> WebhookStore DO /ingest
+-----------------------------+
| Local MCP Bridge (.mcpb) |
| stdio <- Claude Desktop/CLI |
| -> proxy tool calls to /mcp |
| -> SSE listener -> channel |
+-----------------------------+
The system consists of four components:
- Cloudflare Worker -- Webhook reception, signature verification, tenant routing
- TenantRegistry Durable Object -- installation_id to account_id mapping, per-tenant quota management (single instance)
-
WebhookStore Durable Object -- SQLite event persistence, REST/SSE endpoints (per-tenant instance:
store-{accountId}) -
WebhookMcpAgent Durable Object -- MCP Streamable HTTP server, tool definitions (per-tenant instance:
tenant-{accountId})
The local bridge (mcp-server/) is a proxy to the Worker and does not hold data.
| ID | Requirement |
|---|---|
| F1.1 | Receive GitHub webhook payloads at POST /webhooks/github
|
| F1.2 | Verify HMAC-SHA256 signatures via the X-Hub-Signature-256 header |
| F1.3 | Return HTTP 403 on signature mismatch |
| F1.4 | Skip signature verification when no secret is configured |
| F1.5 | Return HTTP 429 when per-tenant quota is exceeded (installation events skip quota check) |
| F1.6 | Auth check order: IP allowlist -> per-IP rate limit -> signature -> tenant resolution -> per-tenant quota -> ingest |
| ID | Requirement |
|---|---|
| F2.1 | Store events with UUID in the WebhookStore DO's SQLite |
| F2.2 | Each event has id, type, payload, received_at, processed fields |
| F2.3 | Maintain trigger_status, last_triggered_at fields (for future trigger functionality) |
Event structure:
{
"id": "uuid",
"type": "issues",
"payload": {},
"received_at": "ISO8601",
"processed": false,
"trigger_status": null,
"last_triggered_at": null
}The WebhookMcpAgent DO provides the following toolset. The local bridge proxies these.
| ID | Tool Name | Args | Returns | Requirement |
|---|---|---|---|---|
| F3.1 | get_pending_status |
None | pending_count, latest_received_at, types | Return a lightweight snapshot of pending events |
| F3.2 | list_pending_events |
limit (1-100, default 20) | Summary array | Return metadata list of pending events (no payload) |
| F3.3 | get_event |
event_id | Full event or error | Return complete payload by UUID |
| F3.4 | get_webhook_events |
limit (1-100, default 20) | Pending event array | Return pending events with full payloads |
| F3.5 | mark_processed |
event_id | success, event_id | Mark an event as processed |
Event summary structure:
{
"id": "uuid",
"type": "issues",
"received_at": "ISO8601",
"processed": false,
"trigger_status": null,
"last_triggered_at": null,
"action": "opened",
"repo": "owner/repo",
"sender": "username",
"number": 123,
"title": "Issue title",
"url": "https://github.com/..."
}| ID | Requirement |
|---|---|
| F4.1 | Provide SSE stream at GET /events
|
| F4.2 | Broadcast event summaries to all connected SSE clients on webhook ingest |
| F4.3 | Send heartbeat every 30 seconds |
| F4.4 | Clean up on client disconnect |
| ID | Requirement |
|---|---|
| F5.1 | Local bridge declares Claude Code's claude/channel experimental capability |
| F5.2 | Connect to the Worker's SSE endpoint and send notifications/claude/channel on new events |
| F5.3 | Notification content includes event summary (type, repo, action, title, sender) |
| F5.4 | Include meta field with chat_id, message_id, user, ts |
| F5.5 | Channel notifications can be disabled via WEBHOOK_CHANNEL=0 environment variable (default: enabled) |
| F5.6 | Channel notifications are one-way (read-only), no reply tool is provided |
| Step | Operation |
|---|---|
| 1 | Poll get_pending_status() every 60 seconds |
| 2 | If pending_count > 0, fetch summaries via list_pending_events()
|
| 3 | Retrieve full payload only for events that need it via get_event(event_id)
|
| 4 | Mark completed via mark_processed(event_id)
|
| ID | Requirement |
|---|---|
| N1.1 | Webhook secret is managed as a Cloudflare Worker Secret (GITHUB_WEBHOOK_SECRET) |
| N1.2 | HMAC-SHA256 signature verification prevents spoofing |
| N1.3 | Local bridge uses stdio transport with no network exposure |
| N1.4 | Webhook endpoint uses defense-in-depth against DDoS/billing attacks: IP allowlist -> per-IP rate limit -> signature -> tenant resolution -> per-tenant quota |
| N1.5 | GitHub IP allowlist (from api.github.com/meta hooks field) blocks non-GitHub IPs at the outermost layer (github-ip.ts) |
| N1.6 | Per-IP rate limit uses in-memory sliding window within Worker isolate (rate-limit.ts) |
| N1.7 | Per-tenant quota is managed via atomic check-and-increment in TenantRegistry DO, preventing unbounded storage consumption by a single tenant |
| N1.8 | Cloudflare WAF custom rules recommended for external IP blocking (blocks before reaching Worker, reducing CPU billing) |
| ID | Item | Source | Default |
|---|---|---|---|
| N2.1 | Worker URL |
WEBHOOK_WORKER_URL env var |
None (required) |
| N2.2 | Secret | Cloudflare Secret GITHUB_WEBHOOK_SECRET
|
None (verification skipped) |
| N2.3 | Channel notifications | WEBHOOK_CHANNEL |
Enabled (0 to disable) |
| N2.4 | Custom domain | github-webhook.smgjp.com |
Configured as Cloudflare Worker custom domain |
| N2.5 | Authentication | Worker self-auth | No Cloudflare Access. Worker handles webhook secret + OAuth |
| N2.6 | Preview instance |
preview environment |
Same configuration as production for testing |
| ID | Constraint |
|---|---|
| N3.1 | WebhookStore / McpAgent DOs are per-tenant instances (idFromName("store-{accountId}") / getAgentByName("tenant-{accountId}")). TenantRegistry DO is a single instance managing installation-account mappings for all tenants |
| N3.2 | SSE connections are managed in DO memory (disconnected on DO eviction) |
| N3.3 | Local bridge reuses Worker sessions per tool call (auto-retry on session expiry) |
| N3.4 | On OAuth callback, accessible_account_ids (user + orgs) from GET /user/installations are stored in GitHubUserProps, enabling McpAgent to query multiple stores in parallel and merge results so org installation events are visible from member MCP sessions |
| Trigger | Job | Content |
|---|---|---|
| PR to main / push to main | test | Node.js syntax check |
| Trigger | Job | Content |
|---|---|---|
v* tag push |
build-mcpb | Generate .mcpb via mcpb pack
|
v* tag push |
release | Create GitHub Release + attach .mcpb |
v* tag push |
npm-publish | Publish to npm registry |
Release flow:
- Push a
v*tag - CD runs automatically: .mcpb build -> release creation -> .mcpb attachment -> npm publish
- Version is synced automatically from tag name at npm publish time (no manual package.json update needed)
- Pre-release tags (containing
-) are published withnextdist-tag; stable releases uselatest
| Package | Purpose |
|---|---|
| agents | Cloudflare Agents SDK (McpAgent) |
| @modelcontextprotocol/sdk | MCP SDK |
| zod | Schema validation |
| Package | Purpose |
|---|---|
| @modelcontextprotocol/sdk | MCP SDK (Server class direct use) |
| eventsource | SSE client |
Requires Node.js >= 18.0.0.
| Path | Purpose |
|---|---|
worker/src/index.ts |
Cloudflare Worker entry point |
worker/src/agent.ts |
WebhookMcpAgent DO (MCP tool definitions, per-tenant) |
worker/src/store.ts |
WebhookStore DO (SQLite + SSE, per-tenant) |
worker/src/tenant.ts |
TenantRegistry DO (installation-account mapping, quota management) |
worker/wrangler.toml |
Worker deploy configuration |
shared/src/types.ts |
Shared type definitions |
shared/src/summarize.ts |
Event summary generation |
local-mcp/src/index.ts |
Local bridge (TypeScript, development) |
mcp-server/server/index.js |
Local bridge (JS, .mcpb distribution) |
mcp-server/manifest.json |
MCPB manifest |
mcp-server/package.json |
npm package definition |
GitHub | npm | Discussions