buildmymcpserver/BuildMyMCPServer_MASTER_PROMPT.md

34 KiB
Raw Blame History

🎯 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)

// 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)):

  1. /dashboard — Übersicht. Cards: Servers (Anzahl + Status), Calls this period (Progress-Bar vs Quota), Recent Builds, Quick-Action "New Server".

  2. /servers — Liste aller MCP-Server. Tabelle mit Name, Status, Calls (24h), Last Build, Actions. Filter + Search.

  3. /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.
  4. /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
  5. /settings — Org/User-Settings, Billing (Stripe Customer Portal embed), API Keys (für CLI), Team Members (Team+).

  6. /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: <CodeBlock> mit Shiki highlighting + Copy-Button, <StatusPill> mit Animations, <StreamingLogs>.


6. MCP-GENERIERUNGS-PIPELINE (das Kernstück)

Template (apps/runner-template/src/server.ts)

Ein parametrisiertes Template das die Generation füllt:

// 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

// 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)


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.