Go to file
Marco Sadjadi 6197ee7f5e
All checks were successful
Deploy to Production / deploy (push) Successful in 1m1s
feat: particle cloud (no discrete dots) + geo-IP country preselect on login
Two coordinated polish moves the owner asked for.

## 1. Hero particle field — "no white dots, just a glow that follows the mouse and is always in motion"

Previous tuning (uPointSize 2.8, uBaseAlpha 0.6) gave discrete indigo
dots that additively saturated to near-white in dense clusters. The
owner wanted no granular dots visible at all — a continuous indigo
cloud that the cursor pulls toward itself.

Changes:

- **Render fragment**: replaced the anti-aliased disc SDF
  (`smoothstep(0.5, 0.42, d)` — hard edge) with a Gaussian falloff
  (`exp(-d * d * 6.0)` — smooth blob, no edge). Each particle is now
  a soft volume that blends seamlessly with neighbours.

- **Sim fragment**: replaced the outward-gradient ring push with a
  mouse-halo attraction. Particles drift toward an ideal radius
  (~0.20) around the cursor, with exp-bell falloff so they don't
  collapse onto the cursor or feel influenced from across the canvas.
  `ringField()` helper is now unused but kept for future use.

- **JS uniforms**: `uPointSize` 2.8→14 (256-tier) / 3.6→20 (128-tier);
  `uBaseAlpha` 0.6→0.055. Individual particles are below the
  perception threshold for "dot" but 65k of them additively composite
  into a continuous cloud. With the much lower per-particle alpha,
  the cumulative brightness never saturates to white.

- **ParticleField tick loop**: asymmetric ring-active fade — `alpha
  = 0.14` ramping in (fast cursor response), `0.012` decaying out
  (slow glow trail after the pointer moves away). Matches the brief
  "glow longer + attractive to mouse but always in motion".

- **ParticleHero index.tsx**: added an always-on indigo radial
  gradient behind the WebGL canvas, so the hero never reads as
  visually empty between frames — the canvas additively paints the
  dynamic cloud on top. Removed the white-dot stipple from the
  static fallback (it was the most likely source of the "weisse
  punkte" complaint for any visitor on the fallback path).

## 2. SMS login — pre-select country picker from visitor's geo-IP

The country picker on `/login` previously defaulted to `'CH'` for
everyone. Visitors from DE / AT / US / etc. had to manually scroll
to their dial code — small friction but it sits on the highest-stakes
conversion step in the funnel.

- **New API route** `apps/api/src/routes/geo.ts` →
  `GET /v1/geo/country` returns `{ country: 'CH' | 'DE' | … | null }`
  by reading Cloudflare's `CF-IPCountry` header. Public, no auth —
  reading a 2-letter country code from a geo-IP header isn't PII
  under GDPR / DSG. `'XX'` and `'T1'` (CF's "unknown" + Tor) are
  normalised to `null`. Outside CF (dev), header is missing → null.

- **Login page** picks up the result in the existing `useEffect`,
  guards against codes not in our country list, and calls `setCountry`
  to override the `'CH'` default. Stays at `'CH'` if the detection
  fails or the visitor is on a Tor exit. Verified live: the endpoint
  returns `{"country":"DE"}` from CF's German edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:17:20 +02:00
.gitea/workflows fix(deploy): rework prod artifacts to match the actual Hetzner box 2026-05-21 17:48:57 +02:00
apps feat: particle cloud (no discrete dots) + geo-IP country preselect on login 2026-05-27 13:17:20 +02:00
infra/nginx feat(deploy): nginx vhost serves :443 with a self-signed origin cert 2026-05-21 18:10:22 +02:00
ops/bmm ops: backup hardening + restore drill + self-hosted uptime monitor 2026-05-26 23:46:42 +02:00
packages feat: oauth refresh-token grant + per-runner subdomain TLS plumbing 2026-05-25 22:09:06 +02:00
remotion feat: particle cloud (no discrete dots) + geo-IP country preselect on login 2026-05-27 13:17:20 +02:00
scripts fix(tls): pivot per-runner TLS to path-routing on single subdomain 2026-05-25 22:51:30 +02:00
.dockerignore feat(deploy): production Dockerfiles, compose stack, and runbook 2026-05-21 00:37:02 +02:00
.env.example feat(auth): GitHub OAuth login + SMS one-time-code login 2026-05-21 22:59:58 +02:00
.env.production.example fix(deploy): rework prod artifacts to match the actual Hetzner box 2026-05-21 17:48:57 +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
DEPLOY.md feat(deploy): nginx vhost serves :443 with a self-signed origin cert 2026-05-21 18:10:22 +02:00
docker-compose.prod.yml fix(deploy): rework prod artifacts to match the actual Hetzner box 2026-05-21 17:48:57 +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(web): hero redesign — cycling step rotator + full-width video section 2026-05-27 12:05:28 +02:00
pnpm-workspace.yaml feat(web): Remotion hero video — Section 2 (prompt → server → connect) 2026-05-27 10:57:08 +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.