Sprint 3.5: close every dead link and replace the single-step wizard with the spec-mandated 3-step flow. Wizard: - Step 1 collects prompt + name + slug, calls /v1/servers/preview. - Step 2 renders parsed tools (name, description, input schema as copyable JSON) + a credential field per requiredSecret Claude actually identified. Self-contained servers see 'No credentials needed' instead of generic Notion placeholders. - Step 3 streams the live build over WebSocket and shows install snippets. New dashboard pages: - /settings — org, plan/usage, members table, API keys + billing stubs (Sprint 4), encryption status. Reads /v1/me/org. - /audit — filterable table over /v1/audit with action pills, resource refs, IP, metadata JSON. Docs site (/docs + 6 sub-pages): - Sticky 240px sidebar, max-w-prose article column, shared DocsTitle/H2/Code primitives. - Quickstart, MCP concepts, OAuth 2.1 flow (full walkthrough with curl), Authoring tools, Self-hosting, API reference, FAQ. Marketing pages: - /changelog with tagged release timeline. - /security with 8 pillars + disclosure. - /privacy with GDPR-aware sections. - /terms (10 clauses). - /pricing full page (nav now points here instead of /#pricing anchor). - /status with live 10s probes against /api/health and /login. Footer 'system status' badge now links to /status. All 20 routes 200 OK in smoke crawl. Typecheck clean across packages.
150 lines
8.1 KiB
Markdown
150 lines
8.1 KiB
Markdown
# CHOICES.md
|
||
|
||
Decisions made during the autonomous Sprints 1–3 build where the spec was ambiguous or
|
||
where production-quality required a concrete pick.
|
||
|
||
## Stream transport: WebSocket over SSE
|
||
Spec lists both in different sections. The user's goal explicitly says
|
||
"WebSocket-streamed build flow", so the dashboard subscribes to build events via
|
||
`/v1/builds/:id/stream` WebSocket on the control-plane API. Generator-worker publishes
|
||
events to Redis pub/sub; API fans out per-WebSocket subscriber.
|
||
|
||
## Local container deploy (no Coolify in dev)
|
||
Spec targets Coolify on Hetzner for production. In dev the generator spawns containers
|
||
directly via the local Docker daemon (Docker Engine API on `/var/run/docker.sock` or
|
||
`npipe:////./pipe/docker_engine` on Windows). Each generated server gets a host port from
|
||
the `RUNNER_PORT_RANGE_*` window and is exposed at `http://localhost:<port>`. The
|
||
`coolify` adapter is interface-compatible so swapping in for production is a single import.
|
||
|
||
## Local addressing: ports not wildcard subdomains
|
||
`*.mcp.buildmymcpserver.com` requires DNS + Traefik + wildcard SSL — pointless on
|
||
localhost. Dev uses `http://localhost:<port>` for each generated container. Production
|
||
Traefik routing is wired but unused by `pnpm dev`.
|
||
|
||
## OAuth 2.1 Authorization Server lives in the control plane
|
||
The runner-template is a Resource Server only. The control-plane API is the AS:
|
||
issues codes, exchanges tokens, signs RS256 JWTs, exposes JWKS. Each generated server
|
||
verifies tokens against `${CONTROL_PLANE_URL}/oauth/jwks`. This matches the spec's
|
||
"token exchange not pass-through" mandate.
|
||
|
||
## Auth: tight in-house magic-link instead of Better-Auth dependency
|
||
Spec names Better-Auth. In practice Better-Auth adds a large surface (plugins, adapter
|
||
config, cookie middleware) that we'd have to vendor-wrap to share between Fastify
|
||
control-plane and Next.js Server Actions. For a 3-sprint MVP we wrote a ~150-line
|
||
magic-link + session module in `packages/auth` directly on top of the Drizzle schema:
|
||
hash-only token storage, 32-byte CSPRNG tokens, 15-min link TTL, 30-day session TTL,
|
||
auto-org bootstrap on first sign-in. The seams are clean — swapping in Better-Auth
|
||
later means changing `packages/auth/src/index.ts` only. In dev, magic-link URLs are
|
||
printed to the API stdout for the developer to click.
|
||
|
||
## Generator: Claude API call gated by env, mock fallback for offline dev
|
||
If `ANTHROPIC_API_KEY` is set, the worker calls the real Claude API
|
||
(`claude-opus-4-7`). If not, a deterministic mock returns a small "echo" tool so the
|
||
end-to-end build flow stays demoable without burning credits. The choice is logged in
|
||
build logs so users always know which path ran.
|
||
|
||
## Static checks: tsc + targeted regex scan
|
||
- `tsc --noEmit` on the rendered server code in a tmp dir.
|
||
- Regex scan for banned tokens: `eval(`, `Function(`, `child_process`, `fs.unlink`,
|
||
prompt-injection markers (`ignore previous instructions`, `disregard the above`).
|
||
- esbuild bundle produced for the runner so the image is one file + deps.
|
||
|
||
## Sandboxing: dev relaxed, prod flags documented
|
||
Dev containers run without `--read-only` / `--cap-drop=ALL` because Windows Docker
|
||
Desktop refuses some combinations. The production launch flags are baked into
|
||
`apps/generator/src/lib/deploy.ts` constants but commented `// prod-only` for clarity.
|
||
|
||
## ChatGPT install snippet
|
||
ChatGPT supports MCP via "Custom GPTs → Actions → Add MCP server" or the
|
||
research "Custom Connectors" surface. We render a copy block with `URL`,
|
||
`Auth: OAuth 2.1`, and a link to the install-flow page on chat.openai.com. If the
|
||
official deep link path changes, only `apps/web/lib/install-snippets.ts` needs editing.
|
||
|
||
## Tailwind v4 + shadcn registry
|
||
Tailwind v4 uses CSS-first config. shadcn components are vendored into
|
||
`apps/web/components/ui/*` — only the primitives the spec lists. No drop-in defaults
|
||
left untouched: every component has been re-styled against the design tokens.
|
||
|
||
## Stripe / metering / quotas: scaffolded only in Sprint 1–3
|
||
The schema + types are complete. Wired Stripe checkout / webhooks are Sprint 4
|
||
per spec, so we leave a clean seam in `apps/api/src/routes/billing.ts` but do not
|
||
implement the flow yet. Quotas are read-only in the dashboard.
|
||
|
||
## Drizzle: `migrate` strategy
|
||
Schema lives in `packages/db/src/schema.ts`. `pnpm db:push` is dev-only and pushes
|
||
schema directly. Migrations are generated via `pnpm db:generate` and applied via
|
||
`pnpm db:migrate` on first API boot. Local Postgres comes from docker-compose.
|
||
|
||
## Node 20 LTS, ESM everywhere
|
||
All packages are `"type": "module"`. `tsx` runs ts directly in dev. esbuild bundles
|
||
for prod. No CJS.
|
||
|
||
## Conventional commits per task, single branch
|
||
Single `main` branch, conventional-commits messages. No PR flow because this is one
|
||
autonomous run.
|
||
|
||
---
|
||
|
||
# Sprint 3.5 — Real 3-step wizard, filled-in pages
|
||
|
||
Done after live-running the Sprint 1-3 build and finding two UX gaps:
|
||
1. The wizard was a single-step that asked for secrets the user didn't necessarily need.
|
||
2. Half the nav links 404'd because the marketing/dashboard surfaces had un-built routes.
|
||
|
||
## Two-phase generation (preview → cache → build)
|
||
The wizard now runs Claude twice in spirit but **only once on the wire**:
|
||
- Step 1 → `POST /v1/servers/preview` calls Claude synchronously, validates the spec, and
|
||
caches it in Redis under `preview:<id>` with a 5-minute TTL.
|
||
- Step 2 renders the parsed tools + the credentials *Claude actually identified*.
|
||
No more generic Notion placeholders — the form is the spec.
|
||
- Step 3 → `POST /v1/servers` carries `previewId`. The BullMQ worker reads the cached
|
||
spec from Redis (`loadCachedSpec`) and skips the second Claude round-trip. Saves ~30s.
|
||
|
||
If the cache is missed (TTL expired or restart) the worker regenerates fresh and logs
|
||
a `warn` so the build still completes.
|
||
|
||
## Shared `@bmm/llm` package
|
||
`SYSTEM_PROMPT` + `generateSpec()` + `SpecValidationError` + `BannedPatternError` live
|
||
in `packages/llm`. Both `apps/api` (for the synchronous preview path) and
|
||
`apps/generator` (for the worker path) import the same module. No code duplication, no
|
||
prompt drift between the two surfaces.
|
||
|
||
## Audit log writes
|
||
`apps/api/src/lib/audit.ts` exposes a single `audit()` helper that swallows its own
|
||
errors (audit failures never block the request path). Five write sites:
|
||
- `auth.login`, `auth.logout`, `server.create`, `server.iterate`, `server.delete`.
|
||
- Each entry includes `orgId`, `userId`, `resourceType`, `resourceId`, `metadata`,
|
||
`ipAddress`. The `/audit` UI page reads from `GET /v1/audit?limit=&action=`.
|
||
|
||
## All marketing + dashboard links now resolve
|
||
Built out the previously-404 routes as full pages (no "Coming soon" placeholders):
|
||
- `/docs` + 6 sub-pages (Quickstart, MCP concepts, OAuth flow, Authoring tools,
|
||
Self-hosting, API reference, FAQ) under a static sidebar layout. MDX migration is
|
||
Sprint 4; for Sprint 3.5 they're plain TSX with the new `<DocsTitle>`,
|
||
`<DocsH2>`, `<DocsCode>` primitives.
|
||
- `/changelog` — release timeline with tags (launch / feature / fix).
|
||
- `/security` — eight pillars with bodies (per-server isolation, encryption,
|
||
OAuth 2.1, no-token-passthrough, static checks, container hardening, audit log,
|
||
rate limiting) plus disclosure email and compliance roadmap.
|
||
- `/privacy` — six sections (what we collect, what we don't, where it lives,
|
||
subprocessors, retention, GDPR rights).
|
||
- `/terms` — ten clauses.
|
||
- `/pricing` — full grid of the four tiers + pricing FAQ. Marketing nav now points
|
||
to `/pricing` instead of the landing anchor.
|
||
- `/status` — live JS probes against the dashboard and `/api/health` every 10s.
|
||
|
||
## Settings + Audit dashboard pages
|
||
- `/settings` reads `GET /v1/me/org` and renders five cards: Organization, Plan & usage,
|
||
Members (table), API keys (Sprint 4 stub), Encryption (read-only status).
|
||
- `/audit` reads `GET /v1/audit` and renders a filterable table with action pills,
|
||
resource refs, IP, and metadata JSON.
|
||
|
||
## What's *still* Sprint 4
|
||
- Real Stripe billing (the "Manage billing" button in /settings is intentionally
|
||
disabled).
|
||
- Pagefind search in /docs.
|
||
- MDX migration of the docs pages.
|
||
- Real BetterStack-hosted status board at status.buildmymcpserver.com.
|
||
- Custom domain CNAME validation per server.
|
||
- `bmm` CLI + per-org API keys.
|