# 🎯 BUILD: BuildMyMCPServer β€” Production-Ready SaaS > **Master Prompt fΓΌr Claude Code** > Mission: Baue eine komplette, deploy-ready SaaS-Plattform die aus einem natΓΌrlichsprachigen Prompt einen vollstΓ€ndig gehosteten, OAuth-2.1-geschΓΌtzten MCP-Server (Model Context Protocol) erzeugt, deployed und verwaltet. Endkunden konfigurieren ihn in Claude Desktop, Cursor oder ChatGPT mit einem Klick. --- ## 1. PROJECT IDENTITY & POSITIONING **Produktname:** BuildMyMCPServer **Domain:** buildmymcpserver.com (bzw. die spΓ€tere gewΓ€hlte Variante) **Tagline:** *"Describe your tool. We host the server. AI uses it."* **Untertitel:** *"From prompt to production MCP server in 60 seconds. OAuth 2.1, Streamable HTTP, ready for Claude, Cursor & ChatGPT."* **Zielgruppe primΓ€r:** B2B Mid-Market β€” Operations Leads, Solo-Founder, Tech-Teams in KMUs, Agenturen die fΓΌr Kunden MCP-Server bauen, interne Tooling-Teams in Enterprise. **Pricing-Anker (im UI sichtbar):** - **Hobby:** €0 β€” 1 Server, 100k Tool-Calls/Mo, geteilte Infra, BMM-Subdomain - **Pro:** €49/Mo β€” 5 Server, 1M Calls, Custom Domain, Priority Build - **Team:** €149/Mo β€” 25 Server, 10M Calls, RBAC, Audit-Logs, SLA 99.9% - **Enterprise:** ab €499/Mo β€” Unlimited, BYOC, SSO/SAML, dedicated Cluster **Brand-Voice:** Technisch, prΓ€zise, ohne Marketing-BS. "It's infrastructure, not magic." Dunkel & monochrom mit einem prΓ€zisen Akzent. Kein lila Gradient, kein "AI sparkle". Inspiration: Linear, Vercel, Resend, Railway. **Native-feel, kein generisches AI-Wrapper-UI.** --- ## 2. TECH-STACK (fest, nicht verhandelbar) ### Monorepo (Turborepo + pnpm) ``` buildmymcpserver/ β”œβ”€β”€ apps/ β”‚ β”œβ”€β”€ web/ # Next.js 15 (App Router) β€” Marketing + Dashboard β”‚ β”œβ”€β”€ api/ # Fastify Backend (Control Plane) β”‚ β”œβ”€β”€ generator/ # Generation-Worker (BullMQ-Consumer) β”‚ └── runner-template/ # Template fΓΌr gehostete MCP-Server-Container β”œβ”€β”€ packages/ β”‚ β”œβ”€β”€ db/ # Drizzle ORM + PostgreSQL Schema β”‚ β”œβ”€β”€ mcp-templates/ # Code-Templates fΓΌr generierte Server β”‚ β”œβ”€β”€ ui/ # Shared shadcn/ui components β”‚ β”œβ”€β”€ auth/ # Better-Auth wrapper β”‚ └── types/ # Shared TypeScript types (Zod) β”œβ”€β”€ infra/ β”‚ β”œβ”€β”€ docker/ # Dockerfiles β”‚ β”œβ”€β”€ coolify/ # Coolify-Konfigurationen β”‚ └── traefik/ # Reverse-Proxy-Config └── turbo.json ``` ### Stack-Entscheidungen (final) **Frontend Web:** - Next.js 15 (App Router, RSC, Server Actions) - TypeScript (strict mode) - Tailwind CSS v4 - shadcn/ui (registry-mode, nur was wir brauchen) - Lucide-React (icons) - Framer Motion (sparingly β€” nur fΓΌr state-transitions) - next-themes (dark default, light optional) **Backend:** - Fastify (statt Express β€” schneller, besseres TS) - Drizzle ORM - PostgreSQL 16 - Redis 7 (BullMQ-Queue + Sessions) - Better-Auth (Auth-Layer β€” User-Auth NICHT MCP-OAuth) - Stripe (Subscriptions + Metered-Billing) **MCP-Generierung:** - Anthropic Claude API (claude-opus-4-7 fΓΌr Generation, claude-haiku-4-5 fΓΌr Fixes) - `@modelcontextprotocol/sdk` v1.x (TypeScript) als Basis-Library fΓΌr generierte Server - Zod fΓΌr Tool-Input-Validation - esbuild fΓΌr Build-Pipeline **Hosting / Runtime:** - Coolify (auf Hetzner Dedicated AX52) β€” Container-Orchestrator - Docker (jeder Kunden-MCP-Server = eigener Container) - Traefik (Reverse-Proxy + automatic SSL via Let's Encrypt + per-customer subdomains) - Cloudflare (DNS + DDoS + Cache vor Web) **Transport fΓΌr generierte MCP-Server:** - **Streamable HTTP** (Stand 2025-11-25 spec) β€” der einzige Standard heute - KEIN SSE (deprecated seit 2025-06-18) - Stateless-ready (Sessions in Redis falls nΓΆtig, default stateless) - OAuth 2.1 + PKCE + Dynamic Client Registration + Resource Indicators (RFC 8707) - `.well-known/oauth-protected-resource` Discovery Endpoint - `.well-known/oauth-authorization-server` (OAuth Authorization Server Metadata, RFC 8414) **Observability:** - OpenTelemetry (OTel Collector β†’ Grafana/Loki/Tempo Self-hosted) - BetterStack fΓΌr Uptime-Pings - Sentry fΓΌr Frontend + Backend Errors **CI/CD:** - GitHub Actions - Docker-Build β†’ Push zu GHCR - Coolify Webhook fΓΌr Auto-Deploy --- ## 3. SYSTEM-ARCHITEKTUR (so funktioniert das Ganze) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ END USER β”‚ β”‚ (uses Claude Desktop / Cursor / ChatGPT to call MCP tools) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Streamable HTTP + OAuth 2.1 β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ TRAEFIK (per-customer routing) β”‚ β”‚ {slug}.mcp.buildmymcpserver.com β†’ container β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ KUNDEN-MCP-SERVER (Docker Container, 1 pro Server) β”‚ β”‚ - Generierter Code aus Template + Claude-Output β”‚ β”‚ - @modelcontextprotocol/sdk β”‚ β”‚ - OAuth 2.1 Resource Server β”‚ β”‚ - Connects to: Customer's APIs, DBs, etc. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ (logs, metrics) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CONTROL PLANE (Fastify API) β”‚ β”‚ - User-Auth (Better-Auth) β”‚ β”‚ - Server-CRUD β”‚ β”‚ - Generation-Job-Queue (BullMQ) β”‚ β”‚ - Container-Orchestrierung (Coolify API) β”‚ β”‚ - Billing (Stripe webhooks) β”‚ β”‚ - Tool-Call-Metering (fΓΌr Usage-based-Billing) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚Postgresβ”‚ β”‚ Redis β”‚ β”‚ Coolify β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CUSTOMER (Marco's User) β€” uses Web Dashboard β”‚ β”‚ Next.js 15 Dashboard: β”‚ β”‚ - Prompt to build a new MCP server β”‚ β”‚ - Watch generation stream in real-time β”‚ β”‚ - Manage existing servers (logs, restart, edit) β”‚ β”‚ - Install instructions for Claude/Cursor/ChatGPT β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Kritischer Flow β€” "Prompt to Live Server":** 1. User loggt sich ins Dashboard ein, klickt "New MCP Server" 2. Gibt Prompt ein: *"Create an MCP server that queries my Postgres DB at db.example.com with read-only access to the users and orders tables"* 3. UI fragt strukturiert nach: API endpoints, secrets needed, scopes, tool names 4. Backend pusht Job in BullMQ Queue 5. Generator-Worker: - LΓ€dt MCP-Template - Ruft Claude API mit System-Prompt + User-Spezifikation - Parst generierte Tools/Resources/Prompts (Zod-validated) - Schreibt fertigen TypeScript-Code - Baut Docker-Image via `docker buildx` - Pusht zu interner Registry - Triggert Coolify-Deployment auf `{slug}.mcp.buildmymcpserver.com` - Streamt Status (queued β†’ generating β†’ building β†’ deploying β†’ live) via Server-Sent-Events ans Dashboard 6. User sieht Live-URL + Copy-Paste-Snippets fΓΌr: - **Claude Desktop:** `claude_desktop_config.json` Eintrag - **Cursor:** `mcp.json` Eintrag - **ChatGPT Custom Connector:** URL + OAuth-Setup 7. Erster Tool-Call vom End-User β†’ Traefik β†’ Container β†’ Tool β†’ Response β†’ Metric in Redis β†’ spΓ€ter aggregiert in Postgres fΓΌr Billing --- ## 4. DATABASE SCHEMA (Drizzle, vollstΓ€ndig) ```typescript // packages/db/schema.ts import { pgTable, uuid, varchar, text, timestamp, jsonb, boolean, integer, bigint, index, pgEnum } from 'drizzle-orm/pg-core'; export const planEnum = pgEnum('plan', ['hobby', 'pro', 'team', 'enterprise']); export const serverStatusEnum = pgEnum('server_status', ['draft', 'queued', 'generating', 'building', 'deploying', 'live', 'failed', 'paused']); export const buildStatusEnum = pgEnum('build_status', ['queued', 'running', 'success', 'failed', 'cancelled']); // Tenant/Org-Modell β€” auch wenn Single-User, baust du es Org-fΓ€hig (kein Refactor spΓ€ter) export const organizations = pgTable('organizations', { id: uuid('id').defaultRandom().primaryKey(), slug: varchar('slug', { length: 64 }).notNull().unique(), name: varchar('name', { length: 128 }).notNull(), plan: planEnum('plan').default('hobby').notNull(), stripeCustomerId: varchar('stripe_customer_id', { length: 128 }), stripeSubscriptionId: varchar('stripe_subscription_id', { length: 128 }), monthlyCallQuota: bigint('monthly_call_quota', { mode: 'number' }).default(100_000), callsThisPeriod: bigint('calls_this_period', { mode: 'number' }).default(0), periodStartsAt: timestamp('period_starts_at').defaultNow(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const users = pgTable('users', { id: uuid('id').defaultRandom().primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 128 }), avatarUrl: text('avatar_url'), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const memberships = pgTable('memberships', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id').references(() => organizations.id, { onDelete: 'cascade' }).notNull(), userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), role: varchar('role', { length: 32 }).default('owner').notNull(), // owner | admin | member | viewer createdAt: timestamp('created_at').defaultNow().notNull(), }); export const mcpServers = pgTable('mcp_servers', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id').references(() => organizations.id, { onDelete: 'cascade' }).notNull(), slug: varchar('slug', { length: 64 }).notNull(), // becomes subdomain name: varchar('name', { length: 128 }).notNull(), description: text('description'), status: serverStatusEnum('status').default('draft').notNull(), currentVersion: integer('current_version').default(0), containerId: varchar('container_id', { length: 128 }), // Coolify ref publicUrl: text('public_url'), // https://{slug}.mcp.buildmymcpserver.com toolsSchema: jsonb('tools_schema'), // generated tools metadata oauthEnabled: boolean('oauth_enabled').default(true), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(), }, (table) => ({ orgSlugIdx: index('idx_servers_org_slug').on(table.orgId, table.slug), })); export const builds = pgTable('builds', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id').references(() => mcpServers.id, { onDelete: 'cascade' }).notNull(), version: integer('version').notNull(), prompt: text('prompt').notNull(), generatedCode: text('generated_code'), status: buildStatusEnum('status').default('queued').notNull(), errorMessage: text('error_message'), startedAt: timestamp('started_at'), finishedAt: timestamp('finished_at'), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const secrets = pgTable('secrets', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id').references(() => mcpServers.id, { onDelete: 'cascade' }).notNull(), key: varchar('key', { length: 128 }).notNull(), encryptedValue: text('encrypted_value').notNull(), // AES-256-GCM, key in env createdAt: timestamp('created_at').defaultNow().notNull(), }); // Per-customer OAuth clients (fΓΌr Dynamic Client Registration) export const oauthClients = pgTable('oauth_clients', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id').references(() => mcpServers.id, { onDelete: 'cascade' }).notNull(), clientId: varchar('client_id', { length: 128 }).notNull().unique(), clientSecretHash: text('client_secret_hash'), // bcrypt redirectUris: jsonb('redirect_uris').notNull(), // string[] metadata: jsonb('metadata'), // RFC 7591 client metadata createdAt: timestamp('created_at').defaultNow().notNull(), }); export const oauthTokens = pgTable('oauth_tokens', { id: uuid('id').defaultRandom().primaryKey(), clientId: uuid('client_id').references(() => oauthClients.id, { onDelete: 'cascade' }).notNull(), accessTokenHash: text('access_token_hash').notNull(), refreshTokenHash: text('refresh_token_hash'), scope: text('scope'), expiresAt: timestamp('expires_at').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const toolCallMetrics = pgTable('tool_call_metrics', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id').references(() => mcpServers.id).notNull(), toolName: varchar('tool_name', { length: 128 }).notNull(), durationMs: integer('duration_ms'), success: boolean('success').notNull(), errorCode: varchar('error_code', { length: 64 }), timestamp: timestamp('timestamp').defaultNow().notNull(), }, (table) => ({ serverTimeIdx: index('idx_metrics_server_time').on(table.serverId, table.timestamp), })); export const auditLog = pgTable('audit_log', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id').references(() => organizations.id), userId: uuid('user_id').references(() => users.id), action: varchar('action', { length: 128 }).notNull(), resourceType: varchar('resource_type', { length: 64 }), resourceId: varchar('resource_id', { length: 128 }), metadata: jsonb('metadata'), ipAddress: varchar('ip_address', { length: 64 }), createdAt: timestamp('created_at').defaultNow().notNull(), }); ``` --- ## 5. UI/UX β€” DAS MUSS NATIVE-FEEL HABEN ### Design-Prinzipien 1. **Monochrom mit Akzent.** Background `#0A0A0B` (fast schwarz aber nicht). Foreground `#FAFAFA`. Border `#1F1F22`. Akzentfarbe: ein prΓ€zises **Electric Indigo `#6366F1`** sparingly fΓΌr CTAs und Status-Live. Kein Lila-Gradient. 2. **Geometric Sans + Mono.** `Geist` fΓΌr UI, `Geist Mono` fΓΌr Code/IDs/URLs. 3. **Tight Density.** Padding 12-16px nicht 24-32px. Compact-feeling wie Linear/Vercel-Dashboard. 4. **Reduced motion.** Nur Skeleton-Loads und State-Changes animieren (200ms ease-out). Keine "hero-spinners". 5. **Code als Hero.** Jede Page hat irgendwo einen Code-Block (Copy-Button immer rechts oben). 6. **Status-Pills.** `live` = green dot pulsing, `building` = amber spinner, `failed` = red, `draft` = grey. ### Pages (komplett) **Marketing (apps/web/app):** 1. **`/` (Landing):** - Hero: Großes Heading *"Describe your tool. We host the server."*, Subline, ein Code-Block der einen Prompt zeigt + Output-Snippet (animiertes Typing-Demo, nicht Video, in HTML). - "How it works" β€” 3 Steps mit jeweils einem kleinen Screenshot/Code-Block. - "Works with" β€” Logos: Claude, Cursor, ChatGPT, VS Code Copilot, Continue.dev. - "Examples" β€” Grid mit 6 Use-Cases (Postgres, Salesforce, Notion, GitHub, Stripe, Custom REST). - Pricing (4 Tiers, transparent, kein "Contact us" außer Enterprise). - FAQ (12 Fragen: Was ist MCP, Brauche ich OAuth, Welche AI-Tools, Kann ich self-hosten, Was passiert wenn ich kΓΌndige, Custom Domain, etc.). - Footer: Status, Docs, GitHub (wenn open-source-Teile), Security/SOC2-roadmap, Privacy, Terms. 2. **`/docs/*`** β€” MDX-basiert, schmaler 280px Sidebar, max-w-prose, Search via Pagefind. Bereiche: Quickstart, Concepts (Tools, Resources, Prompts, OAuth), Recipes, API-Reference, Self-Hosting-Guide. 3. **`/changelog`** β€” MDX timeline. 4. **`/pricing`** β€” wenn aus Landing rausgezogen werden soll. **Dashboard (apps/web/app/(dashboard)):** 5. **`/dashboard`** β€” Übersicht. Cards: Servers (Anzahl + Status), Calls this period (Progress-Bar vs Quota), Recent Builds, Quick-Action "New Server". 6. **`/servers`** β€” Liste aller MCP-Server. Tabelle mit Name, Status, Calls (24h), Last Build, Actions. Filter + Search. 7. **`/servers/new`** β€” DER WIZARD. Drei-Step: - Step 1: Prompt-Eingabe (Textarea mit Beispiel-Prompts als Chips drumherum: *"Postgres reader"*, *"Notion search"*, *"Custom REST API"*). - Step 2: Strukturierte Confirmation. Claude hat parsed, zeigt: Tool-Liste, Required Secrets (Input-Felder, encrypted save), Optional Scopes. - Step 3: Deploy. Live-Stream der Build-Logs (Server-Sent Events von der API). Bei Success: Live-URL + 3 Tabs (Claude Desktop, Cursor, ChatGPT) mit copy-ready Config-Snippets. 8. **`/servers/[id]`** β€” Server-Detail mit Tabs: - **Overview:** Status, URL, OAuth-Status, Quick-Install-Configs - **Tools:** Liste der exposed Tools mit Schemas - **Logs:** Streaming-Logs (Server-Sent Events, last 1000 lines) - **Metrics:** Charts (Calls/h, Latency P50/P95/P99, Error-Rate) β€” Recharts - **Secrets:** Manage env-vars (verschlΓΌsselt) - **Iterate:** Neuer Prompt um Server zu erweitern (= neuer Build, neue Version) - **Settings:** Rename, Custom Domain (Pro+), Pause, Delete 9. **`/settings`** β€” Org/User-Settings, Billing (Stripe Customer Portal embed), API Keys (fΓΌr CLI), Team Members (Team+). 10. **`/audit`** β€” Audit Log (Team+). ### Komponenten-Library Use shadcn/ui registry, install only: `button`, `card`, `dialog`, `dropdown-menu`, `input`, `label`, `select`, `separator`, `sheet`, `table`, `tabs`, `toast`, `tooltip`, `badge`, `skeleton`, `progress`, `command` (fΓΌr CMD+K). Custom: `` mit Shiki highlighting + Copy-Button, `` mit Animations, ``. --- ## 6. MCP-GENERIERUNGS-PIPELINE (das KernstΓΌck) ### Template (apps/runner-template/src/server.ts) Ein parametrisiertes Template das die Generation fΓΌllt: ```typescript // THIS IS A TEMPLATE β€” values in {{ }} get replaced by generator import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { z } from "zod"; import Fastify from "fastify"; import { createRemoteJWKSet, jwtVerify } from "jose"; const server = new McpServer({ name: "{{SERVER_NAME}}", version: "{{SERVER_VERSION}}", }); // {{TOOL_REGISTRATIONS}} // Generated tools go here, e.g.: // server.tool("query_users", "...", { id: z.string() }, async ({ id }) => { ... }); // {{RESOURCE_REGISTRATIONS}} // {{PROMPT_REGISTRATIONS}} const app = Fastify({ logger: { level: 'info' } }); // OAuth 2.1 Protected Resource Metadata (RFC 9728) app.get('/.well-known/oauth-protected-resource', async () => ({ resource: process.env.PUBLIC_URL, authorization_servers: [`${process.env.PUBLIC_URL}/oauth`], bearer_methods_supported: ['header'], resource_documentation: `${process.env.PUBLIC_URL}/docs`, scopes_supported: {{SCOPES_JSON}}, })); // OAuth Authorization Server Metadata (RFC 8414) app.get('/.well-known/oauth-authorization-server', async () => ({ issuer: `${process.env.PUBLIC_URL}/oauth`, authorization_endpoint: `${process.env.PUBLIC_URL}/oauth/authorize`, token_endpoint: `${process.env.PUBLIC_URL}/oauth/token`, registration_endpoint: `${process.env.PUBLIC_URL}/oauth/register`, response_types_supported: ['code'], grant_types_supported: ['authorization_code', 'refresh_token'], code_challenge_methods_supported: ['S256'], token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'none'], })); // OAuth endpoints (delegated to control-plane via internal RPC, or local for stateless) // {{OAUTH_HANDLERS}} // MCP Streamable HTTP endpoint const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // stateless mode }); app.all('/mcp', async (request, reply) => { // Validate bearer token (OAuth 2.1 Resource Server) const auth = request.headers.authorization; if (!auth?.startsWith('Bearer ')) { return reply .code(401) .header('WWW-Authenticate', `Bearer resource_metadata="${process.env.PUBLIC_URL}/.well-known/oauth-protected-resource"`) .send({ error: 'unauthorized' }); } const token = auth.slice(7); try { const JWKS = createRemoteJWKSet(new URL(`${process.env.CONTROL_PLANE_URL}/oauth/jwks`)); const { payload } = await jwtVerify(token, JWKS, { issuer: `${process.env.PUBLIC_URL}/oauth`, audience: process.env.PUBLIC_URL, }); // RFC 8707 Resource Indicators check if (payload.aud !== process.env.PUBLIC_URL) { return reply.code(403).send({ error: 'invalid_audience' }); } } catch (e) { return reply.code(401).send({ error: 'invalid_token' }); } // Forward to MCP transport await transport.handleRequest(request.raw, reply.raw, request.body); }); await server.connect(transport); await app.listen({ port: parseInt(process.env.PORT || '3000'), host: '0.0.0.0' }); ``` ### Generator-Worker Logic ```typescript // apps/generator/src/worker.ts (Pseudocode-Skelett) import { Worker } from 'bullmq'; import Anthropic from '@anthropic-ai/sdk'; const SYSTEM_PROMPT = `You are an expert TypeScript engineer generating production MCP servers. Output ONLY a JSON object with this exact schema: { "tools": [ { "name": "snake_case_name", "description": "Clear description for the AI client", "inputSchema": { /* JSON Schema, strict types */ }, "implementation": "async ({ args }) => { /* full TypeScript body */ }" } ], "resources": [...], "prompts": [...], "requiredSecrets": ["API_KEY", "DATABASE_URL"], "scopes": ["read:users", "write:orders"], "dependencies": { "pg": "^8.13.0" } } Rules: - Use Zod for parameter schemas, NOT JSON Schema directly - All implementations MUST be async, MUST handle errors, MUST return MCP content blocks - NEVER use eval, exec, fs writes outside /tmp - NEVER hardcode secrets β€” use process.env.{SECRET_NAME} - Validate all inputs with Zod before use - Return errors as { content: [{ type: "text", text: "Error: ..." }], isError: true } - For databases: use parameterized queries ONLY - For external APIs: implement retry with exponential backoff - Each tool must be idempotent OR clearly state it is destructive in description `; new Worker('build', async (job) => { const { serverId, prompt, orgId } = job.data; // 1. Generate with Claude const anthropic = new Anthropic(); const response = await anthropic.messages.create({ model: 'claude-opus-4-7', max_tokens: 8192, system: SYSTEM_PROMPT, messages: [{ role: 'user', content: prompt }], }); // 2. Parse + Validate output (Zod schema for the generator output itself) const spec = parseAndValidate(response.content[0].text); // 3. Inject into template const code = renderTemplate(spec); // 4. Run static checks (tsc --noEmit on generated code, lint, basic sandbox-test) await runStaticChecks(code); // 5. Build Docker image const imageTag = `mcp-${serverId}:v${version}`; await dockerBuild(code, imageTag, spec.dependencies); // 6. Deploy via Coolify API await coolify.deploy({ image: imageTag, subdomain: `${serverSlug}.mcp.buildmymcpserver.com`, env: secrets, healthcheck: '/health', }); // 7. Update DB, emit SSE to dashboard await db.update(mcpServers).set({ status: 'live', publicUrl: '...' }).where(...); await redis.publish(`build:${serverId}`, JSON.stringify({ status: 'live' })); }, { connection: redis }); ``` ### Sandboxing & Sicherheit (NON-NEGOTIABLE) - Container laufen mit `--read-only` Filesystem (außer `/tmp` mit Quota) - `--cap-drop=ALL`, `--security-opt=no-new-privileges` - CPU-Limit 0.5 cores, Memory 512MB pro Container default - Network-Egress nur zu whitelisted Domains (definiert via Tool-Spec) - Keine SSH, kein Shell in den Containern - Secrets via Docker Secrets / Coolify Env-Vars (verschlΓΌsselt at rest mit AES-256-GCM in Postgres, KMS-Key in Hetzner Vault oder Doppler) - Rate-Limit pro Tool-Call (default 100/min/IP, configurable) - Prompt-Injection-Defense: Generierte Tool-Descriptions werden auf gefΓ€hrliche Patterns gescannt (z.B. "ignore previous instructions" β†’ reject build) --- ## 7. PFLICHT-FEATURES FÜR PRODUCTION ### MUST-HAVE (MVP-Cut) - [ ] Auth (Email-Magic-Link + GitHub-OAuth via Better-Auth) - [ ] Org-Management mit RBAC - [ ] Server-CRUD - [ ] Prompt-to-Server Wizard mit Live-Build-Stream - [ ] Secret-Management (encrypted) - [ ] OAuth-2.1-Server-Implementation fΓΌr jeden generierten MCP - [ ] Streamable HTTP Transport in jedem Generated Server - [ ] Stripe-Billing (Pro/Team-Subs + Metered Overage) - [ ] Tool-Call-Metering + Quota-Enforcement - [ ] Server Detail mit Logs/Metrics - [ ] Install-Snippets fΓΌr Claude Desktop, Cursor, ChatGPT - [ ] Marketing-Landing - [ ] Docs-Site (10+ Pages) - [ ] Status-Page - [ ] Audit-Log - [ ] Health-Checks + Auto-Restart bei Failure - [ ] Backups (DB + Server-Configs daily zu Backblaze B2) ### SHOULD-HAVE (Phase 2, nach erstem zahlenden Kunden) - [ ] CLI (`buildmymcp init`, `deploy`, `logs`) - [ ] GitHub-Repo-Sync (Server-Code in Customer's Repo pushen fΓΌr Transparenz/Audit) - [ ] Template-Marketplace (Pre-Built MCP-Server fΓΌr gΓ€ngige Tools) - [ ] Custom-Domain per Server (CNAME-Validation) - [ ] Team-Member-Invites - [ ] Webhooks fΓΌr Build-Events - [ ] SSO/SAML fΓΌr Enterprise ### NICE-TO-HAVE (Phase 3) - [ ] BYOC (Bring Your Own Cloud β€” Deploy in Customer's AWS/GCP/Azure) - [ ] MCP-Registry-Publishing (Servers public listen) - [ ] AI-Powered Test-Generation (Tests fΓΌr jeden generierten Tool) - [ ] Multi-Region Deployment --- ## 8. DEPLOYMENT (production-ready ab Tag 1) ### Hetzner-Setup - **1Γ— AX52 Dedicated** (€59/Mo): 16 Cores / 64GB RAM β€” running Coolify + Postgres + Redis + Generator + Customer-Containers (capacity: ~300-500 small MCP-Server) - **1Γ— CX22 Cloud** (€5/Mo) als Backup-Target + Status-Page - **Cloudflare Free Plan** fΓΌr DNS + DDoS - **Backblaze B2** fΓΌr DB-Backups (~€1/Mo bis 10GB) ### Domains-Plan - `buildmymcpserver.com` β€” Marketing + Dashboard - `api.buildmymcpserver.com` β€” Control-Plane API - `*.mcp.buildmymcpserver.com` β€” Customer MCP-Server (Wildcard SSL via Let's Encrypt DNS challenge mit Cloudflare API) - `docs.buildmymcpserver.com` (oder /docs auf Hauptdomain) - `status.buildmymcpserver.com` (BetterStack hosted) ### Environment-Variables (.env.example) ``` # Control Plane DATABASE_URL=postgresql://... REDIS_URL=redis://... BETTER_AUTH_SECRET= BETTER_AUTH_URL=https://buildmymcpserver.com GITHUB_OAUTH_ID= GITHUB_OAUTH_SECRET= STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= STRIPE_PRICE_PRO= STRIPE_PRICE_TEAM= ANTHROPIC_API_KEY= SECRETS_ENCRYPTION_KEY= # 32 bytes hex, AES-256-GCM COOLIFY_API_URL=https://coolify.internal/... COOLIFY_API_TOKEN= TRAEFIK_API_URL= CLOUDFLARE_API_TOKEN= SENTRY_DSN= OTEL_EXPORTER_OTLP_ENDPOINT= # Per-MCP-Container (injected by generator/coolify) PUBLIC_URL=https://{slug}.mcp.buildmymcpserver.com CONTROL_PLANE_URL=https://api.buildmymcpserver.com SERVER_ID= PORT=3000 ``` ### CI/CD (GitHub Actions) - `pnpm typecheck` (alle Pakete, strict) - `pnpm test` (Vitest) - `pnpm lint` (eslint + biome) - `pnpm build` (turbo) - Docker-Build mit cache - Push zu GHCR - Coolify-Webhook trigger - Smoke-Test gegen Staging - Promote zu Prod --- ## 9. WAS DU IN DIESER REIHENFOLGE BAUST **Sprint 1 (Woche 1): Foundation** 1. Monorepo-Setup (turbo, pnpm, tsconfig, biome, prettier) 2. DB-Schema + Drizzle-Migrations 3. Better-Auth-Setup mit Magic-Link + GitHub 4. Web-App-Shell (Layout, Theme, Auth-Pages, leeres Dashboard) 5. Marketing-Landing (Hero, Pricing, FAQ β€” kann statisch) **Sprint 2 (Woche 2): Generation-Pipeline** 6. MCP-Template (apps/runner-template) β€” fertig lauffΓ€hig stdio + Streamable HTTP 7. Generator-Worker mit BullMQ + Claude-API-Call 8. Static-Checks-Pipeline (tsc, lint, security-scan) 9. Docker-Build-Pipeline (lokal mit buildx) 10. Coolify-API-Integration zum Deploy **Sprint 3 (Woche 3): OAuth + Dashboard** 11. OAuth-2.1-Implementation (Control-Plane als Authorization Server, Per-Server-Container als Resource Server) 12. JWKS Endpoint + Token-Signing (asymmetric, RS256) 13. Dashboard: New-Server-Wizard mit Live-SSE-Stream 14. Server-Detail-Page mit Logs/Metrics 15. Install-Snippets-Generator (Claude/Cursor/ChatGPT-Configs) **Sprint 4 (Woche 4): Billing + Production** 16. Stripe-Integration (Subscriptions, Webhooks, Metered Usage) 17. Quota-Enforcement 18. Audit-Log 19. Docs-Site (10+ MDX-Pages) 20. Status-Page-Integration 21. Backup-Cron 22. End-to-End Smoke-Test 23. Production-Deploy --- ## 10. ACCEPTANCE CRITERIA β€” WANN BIST DU FERTIG βœ… User kann sich registrieren, einloggen, sieht ein leeres Dashboard. βœ… User klickt "New Server", gibt Prompt ein wie *"Create an MCP server that exposes a 'search_docs' tool which queries my Notion via API"*, gibt Notion-API-Key ein, sieht Build-Stream live. βœ… Nach <90 Sekunden: Live-URL `xyz.mcp.buildmymcpserver.com/mcp` ist erreichbar, return 401 ohne Token, 200 mit gΓΌltigem Token. βœ… User kopiert Snippet in `claude_desktop_config.json`, startet Claude Desktop neu, OAuth-Flow geht durch, Tool ist verfΓΌgbar in Claude. βœ… User stellt Frage in Claude die `search_docs` triggert, Result kommt zurΓΌck, Call wird in Dashboard-Metrics gezΓ€hlt. βœ… User kann den Server iterieren ("Add a 'create_page' tool"), neue Version wird gebaut, ohne dass die alte runtergeht (rolling deploy). βœ… Stripe-Checkout fΓΌr Pro-Plan funktioniert, Quota wird angehoben. βœ… Bei Quota-Überschreitung: HTTP 429 + Hinweis im Dashboard. βœ… Logs streamen in Echtzeit ins Dashboard. βœ… Audit-Log zeigt alle Aktionen. βœ… Lighthouse: 95+ auf allen Marketing-Pages, 90+ auf Dashboard. βœ… TypeScript strict, 0 `any`, 0 unused imports, biome-clean. βœ… Sentry erfasst Errors, OTel-Traces gehen durch. βœ… DB-Backup lΓ€uft tΓ€glich automatisch. βœ… Generated Server bestehen das offizielle MCP-Test-Suite (`mcp-inspector` + Anthropic's Reference-Client). --- ## 11. WAS DU NIEMALS TUN DARFST ❌ KEIN `eval()` oder `Function()` im Generator-Worker fΓΌr generierten Code β€” immer durch Static-Build-Pipeline. ❌ KEIN Sharing von Containern zwischen Kunden β€” 1 Server = 1 Container = 1 Isolation-Boundary. ❌ KEINE Plain-Text-Secrets in DB β€” immer AES-256-GCM verschlΓΌsselt. ❌ KEINE Verwendung von deprecated SSE-Transport β€” nur Streamable HTTP. ❌ KEINE Token-Pass-Through (Spec verbietet es ausdrΓΌcklich) β€” du musst Token-Exchange machen wenn der MCP-Server downstream-APIs aufruft. ❌ KEINE Storage von OAuth-Access-Tokens im Klartext β€” nur Hashes fΓΌr Verification. ❌ KEIN `*` als CORS β€” explizit pro Server konfigurieren. ❌ KEIN automatisches Deploy von generated Code ohne Static-Checks β€” wenn Checks failen, Build = failed, klare Error-Message. ❌ KEIN Branding wie "Powered by Claude" oder Γ€hnliches im Marketing β€” du bist eine eigenstΓ€ndige Plattform, kein Anthropic-Wrapper. ❌ KEIN "AI-Sparkle-Design" β€” Linear/Vercel-Style. Boring on the outside, magic on the inside. --- ## 12. REFERENCES (FÜR DICH, CLAUDE CODE, IM ZWEIFELSFALL CHECKEN) - MCP Spec: https://modelcontextprotocol.io/specification/2025-11-25/ - MCP Authorization: https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization - TypeScript SDK: https://github.com/modelcontextprotocol/typescript-sdk - FastMCP (alternative wrapper, optional): https://github.com/punkpeye/fastmcp - OAuth 2.1: https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/ - RFC 8414 (Auth Server Metadata): https://datatracker.ietf.org/doc/html/rfc8414 - RFC 9728 (Protected Resource Metadata): https://datatracker.ietf.org/doc/html/rfc9728 - RFC 8707 (Resource Indicators): https://datatracker.ietf.org/doc/html/rfc8707 - RFC 7591 (Dynamic Client Registration): https://datatracker.ietf.org/doc/html/rfc7591 - Coolify Docs: https://coolify.io/docs - Drizzle ORM: https://orm.drizzle.team/ --- ## 13. START Lies diese Spec komplett durch. Stelle RΓΌckfragen wenn etwas mehrdeutig ist. Beginne dann mit Sprint 1, Task 1: Monorepo-Setup. Commit nach jedem abgeschlossenen Task mit conventional-commits-Format (`feat:`, `fix:`, `chore:`, `docs:`). Wenn du an einem Punkt unsicher bist ΓΌber die MCP-Spec, **fetche die offizielle Doku, rate nicht aus dem Speicher.** Die Spec Γ€ndert sich noch hΓ€ufig. **Build it like infrastructure, not like a demo. Production-grade from line 1.** Let's go.