
MCP-Server bauen: Schritt-für-Schritt-Anleitung für REST- und GraphQL-Backends
TL;DR: „In 7 Schritten zum produktionsreifen MCP-Server: Backend kapseln, Tools modellieren, Resources exponieren, OAuth absichern, deployen. Code-Beispiele in TypeScript inklusive."
— Till FreitagWarum diese Anleitung?
Wenn du die strategische Begründung für MCP gelesen hast, weißt du: Ohne MCP-Server bist du in 12 Monaten nicht mehr im Workflow deiner Nutzer. Die Frage ist nur noch: Wie baust du das konkret?
Diese Anleitung zeigt dir den schnellsten Weg von „bestehendes REST/GraphQL-Backend" zu „funktionsfähiger MCP-Server, an dem Claude, Codex und ChatGPT andocken können". Du brauchst dafür keinen Greenfield – wir kapseln, was schon da ist.
Wenn du die Grundlagen noch nicht kennst: Lies vorher kurz MCP für Einsteiger.
Die Architektur in 30 Sekunden
┌──────────────┐ MCP ┌──────────────┐ HTTP ┌──────────────┐
│ AI-Client │ ────────▶ │ MCP-Server │ ────────▶ │ Dein Backend │
│ (Claude etc.)│ │ (Adapter) │ │ (REST/GraphQL│
└──────────────┘ └──────────────┘ └──────────────┘
│
└─ exponiert: Tools, Resources, PromptsDer MCP-Server ist kein Replacement deines Backends. Er ist ein dünner Adapter davor, der deine APIs in der Sprache spricht, die Agenten verstehen.
Voraussetzungen
- Bestehendes REST- oder GraphQL-Backend mit dokumentierten Endpoints
- Node.js 20+ oder Python 3.11+
- Ein Auth-Mechanismus deines Backends (API-Key, OAuth, JWT)
- Eine Hosting-Option für Long-running HTTP (Cloudflare Workers, Railway, Fly, Render, Supabase Edge Functions)
Schritt 1: Tools, Resources und Prompts identifizieren
Bevor du Code schreibst, mach diese eine Liste. Sie ist 80 % der Arbeit:
| MCP-Konzept | Wofür? | Beispiel aus deinem Backend |
|---|---|---|
| Tool | Aktion, die der Agent ausführen darf | POST /deals → create_deal |
| Resource | Daten, die der Agent lesen darf | GET /deals/:id → deal://{id} |
| Prompt | Vordefinierte Vorlage für wiederkehrende Tasks | „Erstelle Wochenreport aus Deals" |
Faustregel: Starte mit den 3 wichtigsten Workflows, nicht mit dem ganzen API-Surface. Lemlist hat mit 3 Tools 700 Kunden geholt. Dein API hat wahrscheinlich 200 Endpoints – ignoriere 197 davon im ersten Wurf.
Schritt 2: Setup mit dem offiziellen TypeScript-SDK
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
npx tsc --initGrundgerüst in src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
const server = new McpServer({
name: "acme-crm",
version: "0.1.0",
});
// ... Tools & Resources hier registrieren ...
const transport = new StreamableHTTPServerTransport();
await server.connect(transport);Schritt 3: REST-Endpoints als Tools kapseln
Pro Tool brauchst du Name, Beschreibung, Input-Schema und Handler. Die Beschreibung ist entscheidend – sie ist der Prompt, an dem der Agent entscheidet, ob er dein Tool nutzt.
server.tool(
"create_deal",
"Erstellt einen neuen Deal im CRM. Nutze dies, wenn der User von einem neuen Kunden, einer Opportunity oder einem Verkaufsabschluss spricht.",
{
title: z.string().describe("Klarer Deal-Titel, z. B. 'Acme Corp – Enterprise Q3'"),
value: z.number().describe("Deal-Wert in Euro, ohne Komma"),
contactId: z.string().describe("ID des Kontakts aus dem CRM"),
},
async ({ title, value, contactId }, { authInfo }) => {
const res = await fetch(`${process.env.BACKEND_URL}/api/deals`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authInfo?.token}`,
},
body: JSON.stringify({ title, value, contactId }),
});
if (!res.ok) {
return {
content: [{ type: "text", text: `Fehler: ${res.status} ${await res.text()}` }],
isError: true,
};
}
const deal = await res.json();
return {
content: [{ type: "text", text: `Deal angelegt: ${deal.id} (${title})` }],
};
},
);Drei Dinge, die hier wichtig sind:
- Beschreibungen sind Prompts – schreib sie für den Agenten, nicht für den Menschen
- Fehler immer als
isError: truezurückgeben, sonst halluziniert der Agent Erfolg authInfo.tokenkommt aus der OAuth-Session (siehe Schritt 5)
Schritt 4: GraphQL-Backends kapseln
Bei GraphQL hast du den Vorteil, dass du die Query genau auf das zuschneiden kannst, was der Agent braucht. Mach das. Gib niemals das ganze Schema frei.
server.tool(
"search_customers",
"Sucht Kunden anhand eines Suchbegriffs. Liefert Name, Email und letzten Kontakt.",
{ query: z.string().min(2) },
async ({ query }, { authInfo }) => {
const gql = `
query Search($q: String!) {
customers(search: $q, limit: 10) {
id name email lastContactAt
}
}
`;
const res = await fetch(`${process.env.BACKEND_URL}/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authInfo?.token}`,
},
body: JSON.stringify({ query: gql, variables: { q: query } }),
});
const { data } = await res.json();
return { content: [{ type: "text", text: JSON.stringify(data.customers, null, 2) }] };
},
);Anti-Pattern: Ein generisches
execute_graphql-Tool, das beliebige Queries entgegennimmt. Damit verlierst du Auth-Granularität, gibst dem Agenten zu viele Optionen und fängst dir Schema-Lecks ein.
Schritt 5: Resources – Daten zum Lesen exponieren
Resources sind URIs, die der Client laden kann, ohne dass der Agent eine Aktion ausführt. Perfekt für kontextuelle Daten, die der Agent „immer dabei" haben soll.
server.resource(
"deal",
"deal://{id}",
async (uri, { id }) => {
const res = await fetch(`${process.env.BACKEND_URL}/api/deals/${id}`);
const deal = await res.json();
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(deal),
}],
};
},
);Beispiele für sinnvolle Resources:
customer://{id}– Kunden-Stammdatenreport://weekly/{date}– wöchentlicher Reportpolicy://refund– statische Policy-Dokumente, die der Agent zitieren soll
Schritt 6: OAuth – der unsexy, aber kritische Teil
Hier scheitern 80 % der ersten MCP-Server. Drei Optionen, sortiert nach Aufwand:
Option A: API-Key per Header (nur für interne MCPs)
Schnell, aber nicht multi-tenant. Für interne Server hinter SSO okay.
const transport = new StreamableHTTPServerTransport({
authenticate: async (req) => {
const key = req.headers.get("x-api-key");
if (key !== process.env.INTERNAL_KEY) throw new Error("Unauthorized");
return { token: key };
},
});Option B: Bearer-Token-Forwarding (B2B-Setups)
Der Client schickt sein eigenes Backend-Token mit. Du leitest es 1:1 weiter. Funktioniert, wenn deine Kunden technisch sind und API-Tokens generieren können.
Option C: OAuth 2.1 mit Dynamic Client Registration (echter SaaS-Standard)
Das ist, was du willst, wenn dein MCP-Server in Claude Desktop, ChatGPT und Cursor automatisch funktionieren soll. Das SDK bringt das mit:
import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
const oauth = new ProxyOAuthServerProvider({
endpoints: {
authorizationUrl: "https://auth.deinbackend.de/oauth/authorize",
tokenUrl: "https://auth.deinbackend.de/oauth/token",
revocationUrl: "https://auth.deinbackend.de/oauth/revoke",
},
verifyAccessToken: async (token) => {
// gegen dein Auth-Backend prüfen
const res = await fetch("https://auth.deinbackend.de/introspect", {
method: "POST",
body: new URLSearchParams({ token }),
});
const data = await res.json();
if (!data.active) throw new Error("Invalid token");
return { token, clientId: data.client_id, scopes: data.scope?.split(" ") ?? [] };
},
getClient: async (clientId) => {
// aus deiner Client-DB laden
return { client_id: clientId, redirect_uris: ["..."] };
},
});Dazu noch die Discovery-Endpoints ausliefern (/.well-known/oauth-authorization-server und /.well-known/oauth-protected-resource) – das SDK übernimmt das, wenn du den Provider registrierst.
Wenn dein Backend noch keinen OAuth-Server hat: Das ist okay. Nutz Auth0, Clerk, WorkOS oder Supabase Auth davor. Bau das nicht selbst.
Schritt 7: Lokal testen mit dem MCP Inspector
Das offizielle Test-Tool – wahrscheinlich das wertvollste Stück der Toolchain:
npx @modelcontextprotocol/inspector npx tsx src/index.tsEs startet eine Web-UI auf localhost:6274, in der du jedes Tool, jede Resource und den OAuth-Flow durchklicken kannst, bevor du Claude überhaupt ranlässt.
Erst wenn das hier grün ist, schaltest du Claude Desktop oder Codex an.
Schritt 8: Deployment
Die Streamable-HTTP-Transport-Variante läuft überall, wo Long-running HTTP geht. Empfehlungen aus unseren Projekten:
| Plattform | Wann nehmen? | Trade-off |
|---|---|---|
| Cloudflare Workers | SaaS-Server für viele Tenants | Globale Edge, aber 30s CPU-Limit pro Request |
| Supabase Edge Functions | Wenn dein Backend eh Supabase nutzt | Auth & DB direkt verfügbar, Deno-Runtime |
| Railway / Fly / Render | Klassischer Long-running Node-Server | Einfach, kein Vendor-Lock-in |
| AWS Lambda + API Gateway | Enterprise mit AWS-Vorgabe | Cold Starts, mehr Boilerplate |
Setze hinter den Server CDN + Rate Limit und log jede Tool-Invocation mit Tenant-ID. Du wirst diese Logs lieben, sobald jemand fragt „Warum hat der Agent das gemacht?".
Schritt 9: Listing & Distribution
Sobald der Server live ist:
- Eintrag in mcp.so und glama.ai/mcp/servers
- Eigene Docs-Seite mit „Connect to Claude"-Button (Deep-Link
claude://mcp/install?...) - Submit für native Marketplaces in Claude und ChatGPT
- Beispiele in deiner Doku, die zeigen, welcher Prompt welches Tool triggert – das ist Onboarding für Agenten und Menschen
Häufige Fehler – und wie du sie vermeidest
| Fehler | Konsequenz | Fix |
|---|---|---|
| Zu viele Tools (>30) | Agent wählt schlecht aus | Auf 5–10 Kern-Tools reduzieren |
| Vage Beschreibungen | Tool wird nie aufgerufen | Beschreibung mit „Nutze dies, wenn..." starten |
| Keine Idempotenz | Doppelte Deals, doppelte Mails | idempotency_key Param ergänzen |
| Auth-Token loggen | DSGVO-GAU | Strukturiertes Logging mit Token-Redaction |
| Schema-Drift | Agent ruft veraltete Felder auf | Zod-Schemas aus Backend-Types generieren |
Was als Nächstes?
Wenn dein erster MCP-Server läuft, hast du den schwersten Teil hinter dir. Die nächsten Iterationen:
- Skills schreiben (
SKILL.md), damit Agenten deinen Server systematisch nutzen - Multi-Tenant-Logging mit OpenTelemetry
- Eval-Suite mit echten Prompts, damit Schema-Änderungen Agenten-Verhalten nicht brechen
Fazit
Ein erster, produktionsreifer MCP-Server ist kein Monatsprojekt. Mit einem bestehenden REST- oder GraphQL-Backend, dem offiziellen SDK und einer der genannten Hosting-Optionen schaffst du das in 1–3 Tagen – mit OAuth in 1–2 Wochen.
Der Punkt ist nicht, perfekt zu sein. Der Punkt ist, da zu sein, bevor deine Konkurrenz es ist.
Die beste Zeit, deinen MCP-Server zu launchen, war gestern. Die zweitbeste ist heute.
Du willst diesen Schritt nicht alleine gehen? Wir bauen MCP-Server für SaaS und interne Plattformen – mit OAuth, Multi-Tenant und sauberem Tool-Design. Lass uns reden →







