MCP-Server selbst entwickeln: Der Leitfaden für Engineering-Teams
Production-grade MCP-Server in TypeScript: Tool-Design, Permission-Modell, Tests und Deployment für interne Systeme im Engineering-Team.
Veröffentlicht: 2026-05-30. Aktualisiert: 2026-05-30.
Das Wichtigste in Kürze
- Ein MCP-Server für euer Jira/GitLab/eigene API funktioniert in Claude Code, Codex und Cursor gleichzeitig - eine Implementation statt drei.
- TypeScript ist mein Default-Stack: bester SDK-Support, gute Typsicherheit, hohe Code-Reuse mit Anthropic-Beispielen.
- Tools, Resources und Prompts gehören als getrennte Module aufgebaut - sonst wird Wartung zur Hölle.
- Permission-Modell von Anfang an: token-basiert pro Tool, Scope-Tags pro Resource. Nicht nachträglich anflicken.
- Ohne Tests + CI/CD ist ein MCP-Server kein Production-System, sondern ein hoffnungsvoller Prototyp.
MCP (Model Context Protocol) ist der offene Standard, der AI-Coding-Tools Zugriff auf interne Systeme verschafft, etwa Jira-Tickets, GitLab-MRs, eigene APIs oder Datenbanken. Statt jede Integration pro Tool neu zu bauen, reicht ein MCP-Server pro System, der überall funktioniert. Aufbau in TypeScript: Tool-Design, Permission-Modell, Tests, Deployment und typische Fallstricke.
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:
- 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.
- 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-reviewmit 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:
mcp-server-jira/
├── src/
│ ├── tools/
│ │ ├── ticket-read.ts # get_jira_ticket
│ │ ├── ticket-comment.ts # add_jira_comment
│ │ └── issue-search.ts # search_issues
│ ├── resources/
│ │ └── ticket-templates.ts # template://ticket/bug
│ ├── auth/
│ │ └── permission-check.ts # token-basierte Permission-Logik
│ ├── clients/
│ │ └── jira-client.ts # Wrapper um Jira REST API
│ ├── server.ts # MCP-Server-Initialisierung
│ └── index.ts # Entry-Point
├── tests/
│ ├── tools/
│ │ └── ticket-read.test.ts
│ └── integration/
│ └── server.test.ts
├── Dockerfile
├── package.json
└── tsconfig.json
Drei Prinzipien stecken in dieser Struktur:
- Tools, Resources, Auth getrennt. Kein 1.000-Zeilen-Server-File. Jede Tool-Definition ist ein eigenes Modul mit eigener Test-Coverage.
- Client-Layer abstrahieren. Der
jira-client.tsist die einzige Stelle, die mit Jiras REST-API spricht. Tool-Implementationen rufen Methoden des Clients auf, nicht direktfetch(). - 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:
import { z } from "zod";
import { Tool, ToolResult } from "@modelcontextprotocol/sdk/types";
import { jiraClient } from "../clients/jira-client";
import { checkPermission } from "../auth/permission-check";
const InputSchema = z.object({
ticketId: z.string().regex(/^[A-Z]+-\d+$/, "Format: PROJECT-123"),
});
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 {
content: [
{
type: "text",
text: JSON.stringify(ticket, null, 2),
},
],
};
},
};
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:
- Token pro Tool-Gruppe, nicht pro Server. Beispiel: ein Token für
jira:read(Tickets lesen), ein anderer fürjira:write(Tickets kommentieren). So kann ein Team Lesezugriff bekommen, ohne Schreibrechte zu vergeben. - Scope-Tags pro Resource. Eine Resource kann markiert sein als
scope:public(jeder),scope:internal(nur authentifiziert) oderscope:restricted(Allowlist erforderlich). - 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:
- Unit-Tests pro Tool. Input-Validation, Permission-Check, Error-Paths. Schnell, isoliert.
- 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.
- 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:
- Image-Push ins Registry (ECR, GHCR)
- Deploy in Staging (mit Smoke-Test)
- Manueller Approve oder automatischer Deploy nach Production
Ohne dieses Gerüst ist der Server kein Production-System, sondern Hoffnung.
Typische Fallen
- God-Token statt Permission-Tabelle. Funktioniert in der Demo, scheitert beim Security-Review. Permission-Modell von Tag 1 mitdenken.
- Server ohne Tests. „Ich hab's manuell durchgeklickt." Drei Wochen später bricht eine Backend-Änderung den Server still.
- Tool-Beschreibungen, die zu generisch sind. Wenn
descriptionzu vage ist, hat der Coding-Agent keine Chance zu wissen, wann er das Tool wählen soll. Schreibt präzise. - Synchrone Calls für langsame Backends. Manche Jira-Queries dauern 5+ Sekunden. Tools sollten async sein und Timeouts setzen - sonst hängt der Agent.
- Logging ohne Korrelation. Tool-Calls ohne Trace-ID lassen sich nicht zu User-Sessions zurückverfolgen. Spätestens beim ersten Incident schmerzt das.
Wann Custom-Development vs. Community-Server?
Für Public-APIs (GitHub, Slack, Notion, …) gibt es oft brauchbare Community-MCP-Server. Die nutze ich selbst - kein Grund, Räder neu zu erfinden.
Custom-Development ist für eure internen Systeme:
- Eigene APIs ohne Public-Doku
- Interne Datenbanken mit eurem spezifischen Schema
- Custom-deployte Tools (interner Build-Server, internes Monitoring)
- Systeme mit eigenem Auth-Modell (SSO, mTLS, eigene Tokens)
Faustregel: wenn das System einen eigenen Namen hat, der nur in eurem Unternehmen vorkommt - Custom-Server.
Nächste Schritte
Wer einen MCP-Server selbst bauen will, kann beim Anthropic-MCP-Repository starten und die TypeScript-Beispiele klonen. Wer's lieber outsourced: ich entwickle production-grade MCP-Server als Festpreis-Projekt - Discovery, Design, Implementation, Deployment, Übergabe. Siehe MCP-Server Custom-Development.
Verwandte Posts: Claude Code Setup-Guide, Bedrock AgentCore + OpenClaw.
Häufige Fragen
Was ist MCP (Model Context Protocol)?
Model Context Protocol ist ein offener Standard für die Kommunikation zwischen AI-Coding-Tools (Claude Code, Codex, Cursor und andere) und externen Systemen. Ursprünglich von Anthropic veröffentlicht, mittlerweile breit unterstützt. Ein MCP-Server für eure Datenbank funktioniert in jedem MCP-fähigen Tool ohne Anpassung.
Wann ist ein eigener MCP-Server sinnvoll?
Drei Kriterien: (1) Mehrere AI-Coding-Tools nutzen dieselbe Datenquelle, (2) der Vendor-Mix könnte sich ändern, (3) ihr braucht Permission-Boundaries und Audit-Logging. Für einen einmaligen Use-Case in einem einzigen Tool ist ein REST-Wrapper-Skript oft günstiger.
Welches SDK ist am ausgereiftesten?
TypeScript und Python sind beide production-ready. Mein Default ist TypeScript (höchste SDK-Reife, beste Anthropic-Beispiele). Python lohnt sich, wenn euer Backend in Python ist oder LangChain-Integration eine Rolle spielt.
Wie lange dauert die Entwicklung eines MCP-Servers?
Einfacher Server (1 internes System, 5-10 Tools, token-Auth): 2-3 Wochen inkl. Tests und Deployment. Komplexer Server (mehrere Systeme, RBAC, eigenes Deployment-Setup): 4-6 Wochen. Wartung pro Jahr: 5-10 Engineer-Tage pro Server.
Wie deploye ich einen MCP-Server in Produktion?
Drei gängige Pattern: (a) Docker-Image lokal pro Engineer (für IDE-integrierte Nutzung), (b) Docker-Container auf ECS/Fargate (für Team-weiten Zugriff), (c) Lambda mit MCP-over-HTTP (für Serverless-Setups). Welches Pattern passt, hängt von eurem bestehenden Stack ab.