Skip to content

Requirements

liplus-lin-lay edited this page Mar 31, 2026 · 1 revision

Requirements Specification

Purpose

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.

Premise

  • 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

Architecture

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:

  1. Cloudflare Worker -- Webhook reception, signature verification, tenant routing
  2. TenantRegistry Durable Object -- installation_id to account_id mapping, per-tenant quota management (single instance)
  3. WebhookStore Durable Object -- SQLite event persistence, REST/SSE endpoints (per-tenant instance: store-{accountId})
  4. 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.

Functional Requirements

F1. Webhook Reception

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

F2. Event Persistence

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
}

F3. MCP Tools

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/..."
}

F4. SSE Real-Time Event Delivery

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

F5. Channel Notifications (Local Bridge)

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

F6. Recommended Polling Flow

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)

Non-Functional Requirements

N1. Security

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)

N2. Configuration

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

N3. Constraints

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

CI/CD

Test (CI)

Trigger Job Content
PR to main / push to main test Node.js syntax check

Release (CD)

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:

  1. Push a v* tag
  2. CD runs automatically: .mcpb build -> release creation -> .mcpb attachment -> npm publish
  3. Version is synced automatically from tag name at npm publish time (no manual package.json update needed)
  4. Pre-release tags (containing -) are published with next dist-tag; stable releases use latest

Dependencies

Cloudflare Worker

Package Purpose
agents Cloudflare Agents SDK (McpAgent)
@modelcontextprotocol/sdk MCP SDK
zod Schema validation

Local Bridge (mcp-server/)

Package Purpose
@modelcontextprotocol/sdk MCP SDK (Server class direct use)
eventsource SSE client

Requires Node.js >= 18.0.0.

Files

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