MCP-Server selbst entwickeln: Der Leitfaden für Engineering-Teams

Production-grade MCP-Server in TypeScript: Tool-Design, Permission-Modell, Tests, Deployment. Wie Engineering-Teams interne Systeme reproduzierbar in Claude Code, Codex und Cursor verfügbar machen.

import BlogFAQ from '../../src/components/BlogFAQ'; import KeyTakeaways from '../../src/components/KeyTakeaways';

<KeyTakeaways items= /

<p data speakable MCP (Model Context Protocol) ist der offene Standard, der euren AI Coding Tools Zugriff auf interne Systeme verschafft — Jira Tickets, GitLab MRs, eigene APIs, Datenbanken. Statt jede Integration in jedem Tool neu zu bauen, baut ihr einen MCP Server pro System, der überall funktioniert. Dieser Leitfaden zeigt, wie ein production grade MCP Server in TypeScript aufgebaut wird: Tool Design, Permission Modell, Tests, Deployment und die Fallstricke, die ich in echten Projekten gesehen habe. </p

Warum überhaupt einen eigenen MCP Server?

Die meisten Engineering Teams nutzen heute mehrere AI Coding Tools parallel: Claude Code für die meisten Tasks, Codex für asynchrone Pipelines, Cursor für UI lastige Arbeit. Jedes dieser Tools braucht Zugriff auf denselben Kontext — Tickets, MRs, Architekturentscheidungen, eigene APIs.

Ohne MCP Server passieren zwei Dinge:

1. Copy Paste Workflows. Engineers kopieren Jira Tickets manuell in den Chat, dann den MR Diff, dann die API Spec. Reibung pro Task, Fehler durch veraltete Inhalte, kein Audit Trail. 2. Vendor spezifische Quick Hacks. Jemand schreibt ein Python Skript für Claude Code, ein anderes für Cursor, das dritte für Codex. Drei Implementationen, drei Wartungslasten, null Standardisierung.

MCP löst beides: eine Server Implementation, die in allen MCP fähigen Tools funktioniert. Du baust den Server einmal — er bedient Claude Code, Codex, Cursor und alles, was kommt.

Was bietet ein MCP Server?

Drei Konzepte definieren die Schnittstelle:

Tools — Aktionen, die der Coding Agent ausführen kann. Beispiel: get jira ticket(id), create gitlab mr(...), query db(sql). Sind die häufigste Komponente. Resources — passive Datenquellen, die der Agent referenzieren kann. Beispiel: template://commit message, spec://api/v2/users. Werden nicht ausgeführt, sondern gelesen. Prompts — vordefinierte Prompt Templates mit Slots, die der Agent oder User aufrufen kann. Beispiel: prompt://code review mit Slot für Diff.

Für die meisten Engineering Setups dominieren Tools. Resources sind nützlich für Templates, Specs, Konstanten. Prompts werden in der Praxis seltener gebraucht.

TypeScript oder Python?

Ich empfehle TypeScript als Default Stack:

SDK Reife: Anthropic SDK für TypeScript ist am ausgereiftesten, hat die meisten Beispiele und ist in der Dokumentation am besten abgedeckt. Typsicherheit: Tools und Resources lassen sich mit Zod oder TypeBox typsicher beschreiben — vermeidet ganze Klassen von Bugs. Code Reuse: Wer schon TypeScript Backends hat, kann Datenmodelle und Auth Helper teilen.

Python ist die richtige Wahl, wenn:

Euer Backend ohnehin in Python ist (FastAPI, Django) und Code Sharing wertvoll wird. LangChain oder LangGraph Integration eine Rolle spielt. Euer Team mit Python schneller iteriert als mit TypeScript.

Beide SDKs sind production ready. Wechselt nicht in der Mitte des Projekts — die Migrationskosten lohnen sich selten.

Die Architektur eines production grade Servers

So sieht meine Default Struktur für einen TypeScript MCP Server aus:

Drei Prinzipien stecken in dieser Struktur:

1. Tools, Resources, Auth getrennt. Kein 1.000 Zeilen Server File. Jede Tool Definition ist ein eigenes Modul mit eigener Test Coverage. 2. Client Layer abstrahieren. Der jira client.ts ist die einzige Stelle, die mit Jiras REST API spricht. Tool Implementationen rufen Methoden des Clients auf, nicht direkt fetch(). 3. Tests sind Pflicht, nicht Kür. Unit Tests pro Tool, plus Integration Tests gegen den MCP Server Endpoint.

Tool Definition: ein konkretes Beispiel

Hier ein vereinfachtes Beispiel für ein get jira ticket Tool:

```typescript import from "zod"; import from "@modelcontextprotocol/sdk/types"; import from "../clients/jira client"; import from "../auth/permission check";

const InputSchema = z.object( );

export const getJiraTicket: Tool = { name: "get jira ticket", description: "Holt ein Jira Ticket inklusive Status, Beschreibung, Kommentaren und Linked Issues.", inputSchema: InputSchema,

async handler(input, context): Promise<ToolResult { await checkPermission(context, "jira:read");

const validated = InputSchema.parse(input); const ticket = await jiraClient.getTicket(validated.ticketId);

return , ], }; }, }; ```

Drei Details, die in der Praxis Probleme vermeiden:

Zod Schema für Input Validation. Verhindert Bugs durch ungültige IDs, die als interner 500 Error landen statt als saubere Fehlermeldung an den Agent. Permission Check vor jeder Aktion. Token basiert, nicht user basiert (der Agent ist nicht der User). Strukturierte JSON Antwort. Coding Agents parsen JSON deutlich besser als Freitext.

Permission Modell: das wird unterschätzt

Der häufigste Fehler bei MCP Server Quick Hacks: kein Permission Modell. Der Server hat einen God Token mit Lese und Schreibrechten auf alles, und das gilt für alle Tools. Sobald Security oder DPO einen Blick reinwerfen, ist Schluss.

Mein Standardansatz:

1. Token pro Tool Gruppe , nicht pro Server. Beispiel: ein Token für jira:read (Tickets lesen), ein anderer für jira:write (Tickets kommentieren). So kann ein Team Lesezugriff bekommen, ohne Schreibrechte zu vergeben. 2. Scope Tags pro Resource. Eine Resource kann markiert sein als scope:public (jeder), scope:internal (nur authentifiziert) oder scope:restricted (Allowlist erforderlich). 3. Audit Log pro Invocation. Jeder Tool Call landet als strukturierter Log Eintrag im SIEM: User, Tool, Input Hash, Output Hash, Permission, Dauer. So weiß Compliance, was der Agent tatsächlich macht.

Ohne dieses Modell darf der Server nicht aus dem Prototypen Status raus.

Deployment: drei realistische Pattern

Pattern 1 — Lokal pro Engineer (für IDE integrierte Nutzung) Docker Container, der per docker run lokal startet. Engineer hat eigenen Token. Einfach, aber nicht teamweit geteilt. Sinnvoll für Tools, die personalisierten Kontext brauchen (z.B. „mein zugewiesenes Ticket").

Pattern 2 — ECS/Fargate (für Team weiten Zugriff) Docker Image im ECR, deployed als Fargate Task. MCP läuft über HTTP Endpoint. Engineers verbinden ihre Tools an die zentrale URL. Single Source of Truth, gemeinsame Auth, gemeinsames Audit Log. Mein Default für produktive Setups.

Pattern 3 — Lambda mit MCP over HTTP (für Serverless Stacks) Funktioniert bei Use Cases mit niedriger Frequenz oder spikiger Last. Vorteil: bezahlt nur was läuft. Nachteil: Cold Starts können bei interaktiven Workflows merklich nerven.

In den meisten Projekten lande ich bei Pattern 2. Pattern 1 ist sinnvoll für persönliche Tools, Pattern 3 für stark variable Last.

Tests: was wirklich abgedeckt sein muss

Drei Test Ebenen sind nicht verhandelbar:

1. Unit Tests pro Tool. Input Validation, Permission Check, Error Paths. Schnell, isoliert. 2. Integration Tests gegen den Server. MCP Client wird gegen den lokalen Server gestartet, ruft Tools auf, verifiziert Antworten. Bestätigt, dass die SDK Integration korrekt ist. 3. Smoke Tests gegen Backend Stubs. Wireshark gegen einen Jira Mock — verifiziert, dass die Client Layer mit dem realen Protokoll spricht. Verhindert, dass eine API Änderung den Server bricht.

End to End Tests gegen das echte Backend sind nice to have, aber selten praktisch (Test Daten, Flakiness). Stattdessen: Contract Tests gegen ein eingefrorenes API Schema.

CI/CD: was die Pipeline tun muss

Mindestumfang pro PR:

Build (TypeScript compile, Docker image build) Unit + Integration Tests Lint (ESLint + Prettier) Type Check Security Scan (Dependency Audit, ggf. Snyk/Trivy)

Bei Main Push zusätzlich: