One Cloudflare Worker, live and serving today. Here’s exactly how it’s built, where the seams are, and the four scoped enhancements that take it forward — with seven decisions to capture.
A business-case-agnostic, multi-tenant, multi-user team workspace exposed as a remote MCP server. One Worker hosts unlimited tenants at $0 incremental each — a tenant is a row, not a deploy.
Every D1 query filters by tenant_id. Tenant isolation is structural, not a check you can forget. — the hard rule the whole design is built around
Host→tenant resolve, HMAC bearer identity, then one gate every tool passes: allowed→feature→role→zod. Audit row on every path.
Artifacts row, FTS5 search, links, tasks, audit, oauth. The source of truth for visibility.
.md bodies keyed <tenant>/<kind>/<id>.md. Last-writer-wins, no git friction.
Tenant cache (60s), oauth clients, one-time invite/auth codes.
A request, end to end (save flow)
sequenceDiagram
autonumber
participant C as MCP Client
participant W as Worker
participant K as KV
participant D as D1
participant R as R2
C->>W: POST /mcp (Bearer)
W->>K: resolve tenant by host
K-->>W: tenant (60s cache, D1 fallback)
W->>W: verify bearer · tenant_id == host
W->>W: dispatch gate (allowed, feature, role, zod)
W->>R: put body
W->>D: upsert index + FTS
W->>D: audit_log (every path)
W-->>C: result
Three gaps the design names itself — none invented here. They’re the openings the roadmap fills.
Where the effort lands across the four enhancements
pie showData title Relative build effort
"R2 sweep + lifecycle (now)" : 15
"App versioning (phased)" : 20
"Vectorize semantic (deliberate)" : 40
"Images attachments (when needed)" : 15
"Stream (deferred)" : 10
Additive, all-Cloudflare, isolation-preserving. The save path gains an async embed; search becomes hybrid; a nightly job keeps R2 and D1 honest.
Save & search, after the enhancements
sequenceDiagram
autonumber
participant W as Worker
participant AI as Workers AI
participant V as Vectorize
participant D as D1
participant R as R2
W->>R: put body (+ customMetadata)
W->>D: upsert index + FTS
W->>AI: embed chunks (async)
AI-->>W: vectors
W->>V: upsert (tenant_id metadata)
Note over W,V: search = FTS ∪ Vectorize → D1 visibility gate
The gate never moves. A vector hit is untrusted recall — every result is re-checked against D1’s tenant + visibility rules before it’s returned. Vectorize is search-space, never the security boundary.
Each is fully scoped in docs/superpowers/ — a deep dive, three design specs, and one executable plan.
customMetadata stamping + two lifecycle rules + a daily Cron list-and-diff sweeper that reconciles R2 against D1 with a grace window. App-level versioned keys give opt-in history (R2 has none natively). Closes a named gap, no new product surface.
Hybrid keyword + meaning, beside FTS5. Load-bearing detail: a tenant_id metadata index created before any insert, every query filtered by it, every hit re-verified in D1. Defer until keyword search demonstrably misses — then the plan is ready.
Transform images straight off the existing R2 bucket — no migration, Free tier (5k transforms/mo). The lightest-touch path to artifact image attachments, reusing the same key convention and tenant invariant.
Hosting recordings, not linking them. Deferred: URL-ingest needs a direct MP4, and Loom/Zoom share links are HTML pages. The pointer model is correct until those links start rotting.
Lowest-risk, highest-leverage first. Nothing here blocks the live service; every step is additive and reversible.
A recommended shape — your call on each gate
Seven decisions decide what ships first. Each one already has a recommended option — confirm or redirect whenever you have a minute.
Capture the decisions →