Skip to content

Architecture

Wiki + Jira-style issue tracker built MCP-native, running entirely on Cloudflare’s edge. Monorepo (pnpm + turbo).

This page is an accurate map of how Projektor is actually built - its surfaces, the service layer, and storage - and it stays honest about what is wired up versus what isn’t yet. It describes the real system as it stands today, not an idealised summary of it.

Projektor runs as a single Cloudflare Worker (apps/api, built on Hono). That one Worker exposes two surfaces over a shared request pipeline: a REST API for the browser SPA, and an MCP endpoint (JSON-RPC 2.0) that is the primary, first-class surface for AI agents. Every request flows through auth middleware - Cloudflare Access JWTs for human browser sessions, hashed API tokens for agents - and then workspace middleware that resolves the tenant from the slug and verifies membership before any handler runs.

State lives entirely on Cloudflare’s edge. D1 (SQLite) holds the relational data - workspaces, users, projects, issues, comments, wiki pages, tokens, activity, and revisions. KV caches Access certs and email lookups. R2 is bound for file attachments. There are no servers and no containers: the whole system deploys as a Worker plus its bound data stores, which is what makes a five-minute self-host possible.

The sections below give the picture in increasing detail - first a diagram, then a layer-by-layer breakdown, then the request flow through a single agent call.

flowchart TB
    subgraph Clients
        AI["AI agents<br/>(Claude, etc.)"]
        Browser["Browser / SPA user"]
        CI["GitHub Actions"]
    end

    subgraph Edge["Cloudflare Edge"]
        Access["Cloudflare Access<br/>(SSO / Zero Trust)"]
        subgraph Worker["projektor-api · Cloudflare Worker (Hono)"]
            direction TB
            MW["Middleware<br/>auth → workspace"]
            REST["REST routes<br/>/api/projects · issues · wiki · comments · workspaces"]
            MCP["MCP endpoint<br/>POST /mcp/:workspaceId<br/>(JSON-RPC 2.0)"]
            BOOT["/bootstrap · /health<br/>(dev convenience)"]
            REG["PluginRegistry<br/>(in-memory)"]
        end
        Pages["projektor-web<br/>(Astro + Preact SPA)"]
    end

    subgraph Storage["Cloudflare Data"]
        D1[("D1 / SQLite<br/>workspaces, users, projects,<br/>issues, comments, wiki, tokens,<br/>activity, revisions")]
        KV[("KV<br/>CF certs,<br/>user-by-email cache")]
        R2[("R2<br/>file attachments<br/>(upload / download API)")]
    end

    Plugins["plugins/github<br/>(defined, NOT wired in)"]

    AI -->|"Bearer token + X-Workspace-Slug"| MCP
    Browser -->|"CF_Authorization cookie"| Access --> REST
    Browser --> Pages
    CI -->|"release artifact"| Worker

    MW --> REST
    MW --> MCP
    MCP --> REG
    REST --> D1
    MCP --> D1
    MW --> D1
    MW --> KV
    REST --> R2
    REG -.->|"never registered"| Plugins

    classDef gap fill:#ffe0e0,stroke:#c00,color:#900;
    classDef stub fill:#fff5d6,stroke:#c90,color:#960;
    class Plugins gap;
    class BOOT stub;
Layer Tech Package Notes
Frontend Astro + Preact apps/web Full SPA - issues, board, sprints, wiki, settings, tokens. Dev-proxies /api + /mcp to :8787.
API / edge runtime Hono on Cloudflare Workers apps/api REST + MCP, two-mode auth, workspace tenancy.
MCP server JSON-RPC 2.0 over HTTP apps/api/src/routes/mcp.ts 69 tools across 17 domains (coordination + project data). Primary surface.
Plugin system Registry + SDK apps/api/src/plugins, packages/plugin-sdk definePlugin / defineMCPTool. Not loaded at runtime.
Data model Drizzle ORM → D1 packages/db Drizzle is the schema and primary query layer; raw DB.prepare remains in the auth/workspace middleware hot path, the dev bootstrap, and a handful of service queries (FTS, counters) where hand-written SQL is clearer.
Shared types TS packages/types HonoEnv, Plugin, MCPTool, PluginContext.
Auth CF Access JWT (RS256) + API tokens (SHA-256) middleware/auth.ts Plus dev bypass via DEV_USER_EMAIL.
Deploy wrangler + GitHub Actions projektor-deploy-example, projektor-workspace projektor publishes a release artifact; a config-only deploy repo downloads + ships it (no submodule).

REST (/api/*) is the SPA’s private contract - versionless, and may change shape between releases without notice. MCP (/mcp/:workspaceId) is the stable public surface for agents. The two are usually kept at parity (see AGENTS.md’s service-layer contract), but a handful of deliberate REST↔MCP differences exist by design - see Contributor conventions → Deliberate REST↔MCP parity exceptions.

  1. AgentPOST /mcp/:workspaceId with Authorization: Bearer pk_… + X-Workspace-Slug.
  2. authMiddleware: CF Access JWT → API-token (D1 hash lookup) → dev bypass.
  3. workspaceMiddleware: resolve slug → load workspace → verify workspace_members row.
  4. MCP router dispatches initialize / tools/list / tools/call → core tool handler → D1.