mcp server
rafoworks
Event-driven integration platform boilerplate on Cloudflare Workers — batteries included, agent-native. Hexagonal, D1/KV/Queues/Workflows/R2, MCP, and an enforced deploy gate.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 26, 2026rafoworks
An opinionated, event-driven integration platform on Cloudflare Workers — batteries included, agent-native.
Most Workers starters give you a router and a hello world. Real integration work — webhooks from vendors, payments, ERPs, carriers — needs more: durable events, idempotency, dead-letter queues, reconciliation, structured observability, a read-only surface an agent can safely query, and a dashboard. rafoworks is that skeleton, distilled from a production platform, with the vendor-specific parts stripped out and one clean, runnable example of each primitive left in.
Why this exists
- Why an integration platform, not a router. Integrations fail in ways a request/response app doesn't: a webhook arrives twice, a downstream is down for an hour, an event gets dropped, two events race. A router has no answer for any of these. rafoworks bakes in the answers — dedup, durable retries, a reconciliation net — so you don't rediscover them in an incident.
- Why event-driven. Turning every inbound webhook into a durable, idempotent event (webhook → queue → workflow → D1) decouples receiving from processing. The queue becomes an outage buffer; the workflow gives you durable, resumable execution; the domain-events arm lets business consumers subscribe without touching ingestion.
- Why opinionated. Every convention here prevents a class of failure that a typechecker can't see — a race, silent drift, a broken contract. They're written down as resilience invariants, each with the trap and the real code that avoids it. Opinionated defaults are the point: you inherit the scars, not the incidents.
- Why Cloudflare Workers. D1, KV, Queues, Workflows, and R2 are one platform, at the edge, with a generous free tier and no servers to run. The whole pipeline in this repo deploys as three small workers.
What's inside
packages/
shared/ domain — entities, ports, pure rules, event envelope, error taxonomy (no I/O)
api/ the Worker — adapters, services, workflows, routes, the composition root
mcp/ a read-only MCP server (service binding → the api's ToolsEntrypoint)
web/ a React + Vite dashboard, served as a Worker via Static Assets
scripts/ the enforcement — deploy-gate, docs-lint, changelog-guard/collate
docs/ durable × dated docs with a machine-readable frontmatter contract
.claude/ hooks (the gate) + the destructive-write deny list
CLAUDE.md the operating manual for humans and agents
The one worked example runs the whole pipeline end to end:
POST /webhooks/example ─▶ ingress (auth, dedup, archive to R2, record) ─▶ [queue]
─▶ dispatch ─▶ OrderSyncWorkflow (durable, idempotent) ─▶ upsert order in D1
─▶ emit domain event ─▶ [domain-events queue] ─▶ business consumers
▲
hourly reconcile cron re-drives anything stuck ┘
A fat webhook (carrying the order) materializes the order directly; a thin one (just an id) makes the workflow fetch it from the source adapter. Swap ExampleSourceAdapter for a real vendor and nothing else changes — that's the point of the ports.
Prerequisites
- Node ≥ 20 and pnpm 10 (
corepack enablegives you pnpm). - A Cloudflare account (free tier is enough) — only needed to deploy, not to run locally.
Setup in 5 minutes
Every environment variable the repo uses is named and documented in two files: .envrc.example (local commands: wrangler, Axiom, scripts) and packages/api/.dev.vars.example (secrets for wrangler dev). Each has a comment saying what it is, where to get it, whether it is required, and a fake example. You only fill in values; an agent reads the names to know where real data (logs, D1, APIs) comes from.
pnpm install
pnpm env:setup # copies the .example files to .envrc and packages/api/.dev.vars (never overwrites), lists what is still empty
$EDITOR .envrc # fill the REQUIRED values (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID); then: direnv allow
pnpm env:check # exits 1 and prints variable NAMES (never values) if a required one is missing
Both real files are gitignored. Never commit them and never print them. When you add an env var to the code, add it to the matching .example file with a comment in the same commit; env-documented.node.test.mjs fails the gate if you forget.
Quickstart — run it locally
No Cloudflare account needed; everything runs in Miniflare.
pnpm install
# secrets for local `wrangler dev` (created by `pnpm env:setup`, see "Setup in 5 minutes")
pnpm env:setup
# create the local D1 schema (the migration is committed under packages/api/drizzle)
pnpm --filter @rafoworks/api db:migrate:local
# boot the api worker on http://localhost:8787 (queues + workflows run locally too)
pnpm dev
Now drive the whole pipeline with one webhook (the token matches .dev.vars):
curl -sX POST http://localhost:8787/webhooks/example \
-H "x-webhook-token: dev-webhook-token" \
-H "content-type: application/json" \
-d '{"event":"order.paid","order_id":"o-1","status":"paid","customer_name":"Ada Lovelace","total_cents":4990}'
# → {"ok":true,"status":"queued"}
# the queue consumer + OrderSyncWorkflow run; a moment later the order is materialized:
curl -s http://localhost:8787/orders/example:o-1
# → {"id":"example:o-1","source":"example","sourceOrderId":"o-1","status":"paid",
# "customerName":"Ada Lovelace","totalCents":4990, ...}
# send the same payload again — deduped, no duplicate (idempotent by construction):
curl -sX POST http://localhost:8787/webhooks/example -H "x-webhook-token: dev-webhook-token" \
-H "content-type: application/json" -d '{"event":"order.paid","order_id":"o-1","status":"paid"}'
# → {"ok":true,"status":"duplicate"}
See it in the dashboard (a second terminal):
VITE_API_BASE=http://localhost:8787 pnpm --filter @rafoworks/web dev
# open the printed URL — "Recent orders" lists what you just created
Quickstart — deploy to your account
npx wrangler login # or set CLOUDFLARE_API_TOKEN (see .envrc.example)
# 1. create the resources (names are yours to choose)
npx wrangler d1 create rafoworks-db
npx wrangler kv namespace create CACHE
npx wrangler r2 bucket create rafoworks-raw-data
npx wrangler queues create webhook-events
npx wrangler queues create webhook-events-dlq
npx wrangler queues create domain-events
npx wrangler queues create domain-events-dlq
# 2. paste the printed database_id / kv id into packages/api/wrangler.toml
# 3. set the api secrets
cd packages/api
npx wrangler secret put EXAMPLE_API_TOKEN
npx wrangler secret put EXAMPLE_WEBHOOK_TOKEN
cd ../..
# 4. apply migrations to the remote D1
pnpm --filter @rafoworks/api db:migrate
# 5. deploy (the gate runs first and blocks on any red check)
pnpm deploy:api
pnpm deploy:mcp
pnpm build && pnpm deploy:web
The green gate
Every change must pass before it ships. Run it any time:
pnpm gate # typecheck + lint + build + test + docs-lint + changelog-guard
A PreToolUse hook runs the same checks before any wrangler deploy and denies the deploy on failure — because wrangler deploy runs none of them on its own. The example ships with 22 tests, including seam tests against a real D1 (vitest-pool-workers).
Mutation testing is the real measure of test strength, and one of this repo's forces. Coverage says a line ran; Stryker mutates the code (flips > to >=, deletes a branch, blanks a string) and re-runs the tests — a mutant that survives is a hole your suite doesn't catch, even at 100% coverage. pnpm mutation:shared scores 100% on the domain rules; pnpm mutation:api guards the core services. It runs as its own CI job, kept out of the gate so it never slows a deploy. The rule: kill the mutant, don't lower the bar.
Documentation
Deep dives on how each piece works — every one grounded in the real code, not hand-waving. Index at docs/README.md.
Understand the platform:
- Architecture — hexagonal + event-driven, the resilience invariants, platform limits.
- The event-driven pipeline — one webhook traced end to end: ingress → queue → dispatch → workflow → D1 → domain events.
- Durable workflows — Cloudflare Workflows:
step.do, resume-after-crash, idempotent steps, deterministic instance ids. - Observability — structured logs → wide events → CF Logs/Traces over OTEL to Axiom, with real JSON and query examples.
- Resilience invariants — the seven invariants, each with the trap and the code that avoids it.
- Testing — unit vs seam tests (against a real D1) and mutation testing.
Do things:
- Add an adapter — a runbook to plug in a real source end to end.
- The MCP server — the read-only agent surface + putting OAuth in front of it.
And the operating manual, CLAUDE.md — the conventions, invariants, and platform limits an agent (or human) works under here.
Extending it
Add an adapter (the common case):
- Define or reuse a port in
packages/shared/src/ports/. - Implement it as a class with constructor injection in
packages/api/src/adapters/, validating the vendor response at the boundary with zod. - Wire it in
packages/api/src/lib/container.ts(the composition root) — the only place concretes meet the environment. - Add a unit test for the mapping and, if it crosses a critical seam, an integration test (see
packages/api/src/services/webhook-ingress.test.ts).
Add a workflow: create a WorkflowEntrypoint in packages/api/src/workflows/, export it from index.ts, add a [[workflows]] block to wrangler.toml, and route to it in services/event-router.ts.
The agent harness
This repo assumes an AI is writing code in it, and instruments accordingly:
- The deploy gate blocks a deploy on any red check (typecheck/lint/build/test/docs/changelog).
- changelog-guard denies a deploy of changed code with no changelog entry — "shipped ⟹ recorded", enforced from git history.
- docs-lint keeps the docs contract (frontmatter, resolving links, no future-intent in durable docs) valid at commit time.
- A deny list blocks destructive production SQL from the agent, even with verbal approval.
For the workflow itself, pair it with rafoflow — an opinionated Shape Up agent fleet for Claude Code:
/plugin marketplace add rafae2k/rafoflow
/plugin install shapeup@rafoflow
Then /cycle "your goal" orchestrates research → shape → bet → scope → build → review → ship, leaving an artifact trail in docs/cycles/. rafoflow ships the method; this repo ships the enforcement that makes it non-optional. Read CLAUDE.md for the full operating manual, and the documentation above for how every piece works.
Contributing
See CONTRIBUTING.md. In short: keep it generic and minimal, pnpm gate green, pnpm mutation green for changed logic.
Built by Rafo. MIT.