Go to file
Marco Sadjadi 9cce4a94c2 fix(security): sovereign-audit — close 2 HIGH + 3 MEDIUM findings
Full reasoning-based audit of all 10 zones. 11 findings, all confirmed real,
zero false positives. 5 fixed now, 6 deferred to a justified backlog.

API-SERVERS-001 (HIGH) — DELETE /v1/servers/:id orphaned the container
  The route deleted the DB row but never stopped the Docker container — it
  kept running forever on its host port, still serving traffic with the
  user's secrets baked into its env. The takedown path got stopContainer in
  an earlier commit; this sibling path was missed. DELETE now tears the
  container down first. Verified: deleted 'gfgfg' — container 23e0c55c gone,
  :4110 connection-refused after.

INFRA-001 (HIGH) — SECRETS_ENCRYPTION_KEY zero-default usable in production
  The AES-256-GCM key defaults to 64 zeros and passes the min(64) check. A
  prod deploy that forgot to set it booted silently with every secret
  encrypted under a public key. config.ts now throws on boot when
  NODE_ENV=production and the key is still the placeholder. Verified: prod
  boot with the zero key is REFUSED.

API-SERVERS-002 (MEDIUM) — WS build stream had no authorization
  GET /v1/builds/:id/stream streamed build logs with no auth, while its REST
  twin checks orgId. Now authenticates from the session cookie and rejects
  builds outside the caller's org. Verified: no cookie -> 'unauthorized';
  cross-org build -> 'not_found'; own build -> streams (no regression).

OAUTH-001 (MEDIUM) — authorization code consumption was not atomic
  The 'already used?' check and the 'mark used' write were separate
  statements — two requests racing the same code could both mint tokens.
  Now a conditional UPDATE ... WHERE consumed_at IS NULL RETURNING; the
  loser of the race gets zero rows and invalid_grant.

OAUTH-002 (MEDIUM) — 'plain' PKCE accepted, contradicting AS metadata
  The AS metadata advertises code_challenge_methods_supported: ['S256'] but
  /oauth/authorize accepted 'plain'. Authorize is now z.literal('S256') and
  pkceVerify dropped the plain branch. Verified: authorize with plain -> 400.

Deferred to backlog (documented in TEMPLATE_SECURITY_AUDIT.md is template-only;
this audit's findings are in the commit + certification):
  GENERATOR-001 — secrets via docker -e (visible in docker inspect); needs
    --env-file rework
  RUNNER-001   — generated containers run as root; needs USER node + build
    re-test
  AUTH-001     — no rate limit on magic-link / oauth register; needs
    @fastify/rate-limit
  GENERATOR-002— allocatePort check/bind race; low, self-heals on rebuild
  AUTH-002     — expired magic_links/sessions/oauth rows never purged; needs
    a cron
  FEATURES-001 — tool-call metering not wired (metrics always 0); Sprint 4
    by plan
2026-05-20 18:15:03 +02:00
apps fix(security): sovereign-audit — close 2 HIGH + 3 MEDIUM findings 2026-05-20 18:15:03 +02:00
packages feat(marketplace): template publish + fork + voting/ranking + admin moderation 2026-05-19 23:22:35 +02:00
scripts chore(dev): bootstrap script wires docker + drizzle push + turbo dev 2026-05-19 00:35:27 +02:00
.env.example feat(admin): password-auth admin panel with 8 pages + 15 API endpoints 2026-05-19 23:01:26 +02:00
.gitignore chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00
biome.json chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00
BuildMyMCPServer_MASTER_PROMPT.md chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00
CHOICES.md feat(web): real 3-step wizard, settings, audit, docs, marketing pages 2026-05-19 18:20:31 +02:00
docker-compose.yml fix: live-run wiring (SDK 1.29, zod 3.25, OAUTH_ISSUER split, alt host ports, web on 3001, log level cast, pino transport) 2026-05-19 00:57:23 +02:00
package.json chore(dev): bootstrap script wires docker + drizzle push + turbo dev 2026-05-19 00:35:27 +02:00
pnpm-lock.yaml feat(llm): extract Claude SYSTEM_PROMPT + generateSpec into shared @bmm/llm package 2026-05-19 18:05:31 +02:00
pnpm-workspace.yaml chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00
README.md chore(dev): bootstrap script wires docker + drizzle push + turbo dev 2026-05-19 00:35:27 +02:00
TEMPLATE_SECURITY_AUDIT.md fix(security): template integration sovereign audit + critical fixes 2026-05-19 23:35:45 +02:00
tsconfig.base.json chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00
turbo.json chore: bootstrap monorepo (turbo, biome, docker-compose, env, CHOICES) 2026-05-19 00:20:15 +02:00

BuildMyMCPServer

Describe your tool. We host the server. AI uses it.

Prompt-to-production MCP servers with OAuth 2.1 and Streamable HTTP. Production-grade infrastructure for hosting Model Context Protocol servers your AI clients (Claude Desktop, Cursor, ChatGPT) can install with a copy-paste snippet.

Quick start

# 1. Install
pnpm install

# 2. Copy env. Defaults work for local dev. Set ANTHROPIC_API_KEY if you want real generation.
cp .env.example .env

# 3. Boot everything
pnpm dev

pnpm dev will:

  1. Load .env.
  2. docker compose up -d --wait postgres + redis.
  3. Push the Drizzle schema (drizzle-kit push --force).
  4. Start the full stack in parallel: web (Next.js, :3000), api (Fastify, :4000), generator (BullMQ worker).

Then open:

Click Start building, enter your email, copy the magic-link URL printed to the api terminal output, paste it in your browser. You land on /dashboard. Click New server, paste a prompt, and watch the build stream live over WebSocket.

If ANTHROPIC_API_KEY is unset, the generator returns a deterministic mock spec (an echo and a now tool) so the full end-to-end flow stays demoable.

If Docker is unavailable, the build will fail at the deploy step with a clear error. Otherwise: a fresh container is launched on a host port from RUNNER_PORT_RANGE_START…RUNNER_PORT_RANGE_END, the server is marked live, and the dashboard renders install snippets for Claude Desktop, Cursor and ChatGPT.

Architecture

See BuildMyMCPServer_MASTER_PROMPT.md for the full specification and CHOICES.md for decisions made during this Sprints 13 build.

apps/
  web/              Next.js 15 dashboard + marketing landing
  api/              Fastify control plane (auth, server CRUD, OAuth 2.1 AS, JWKS, WS stream)
  generator/        BullMQ worker — Claude → spec → render → docker build → local deploy
  runner-template/  Hosted MCP server template (Streamable HTTP + OAuth 2.1 RS)
packages/
  db/               Drizzle schema + client
  auth/             Magic-link + session
  types/            Shared Zod contracts

Scripts

Command Effect
pnpm dev Bootstrap + parallel dev for web, api, generator
pnpm dev:no-docker Skip docker-compose (assumes postgres + redis already up)
pnpm build Turbo build all apps
pnpm typecheck Turbo typecheck all apps
pnpm lint Biome check
pnpm lint:fix Biome check --write
pnpm db:push Push schema to postgres (drizzle-kit)
pnpm db:generate Generate SQL migration files
pnpm db:migrate Apply pending migrations
pnpm stop docker compose down

Acceptance check

After pnpm dev is up:

  • http://localhost:3000 renders the landing page.
  • http://localhost:4000/health returns { "ok": true }.
  • Sign in via magic link (URL printed in the api terminal).
  • New Server → paste prompt → live WebSocket stream queued → generating → building → deploying → live.
  • If Docker is running, a container is launched and http://localhost:<port>/mcp responds 401 + WWW-Authenticate without a token, 200 with a valid token issued by /oauth/token.
  • Install snippets render with copy buttons for Claude Desktop, Cursor, ChatGPT.

Repo conventions

  • TypeScript strict, zero any (Biome lints noExplicitAny as error).
  • ESM-only, Node 20 LTS.
  • Conventional commits.
  • Tailwind v4 (@import 'tailwindcss').
  • Geist + Geist Mono.