buildmymcpserver/CHOICES.md
Marco Sadjadi 09688c1114 feat(web): real 3-step wizard, settings, audit, docs, marketing pages
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.
2026-05-19 18:20:31 +02:00

150 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CHOICES.md
Decisions made during the autonomous Sprints 13 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 13
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.