@@ -0,0 +1,740 @@
# 🎯 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: `<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:
``` 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.