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

8.1 KiB
Raw Permalink Blame History

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.

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=.

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.