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:

  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:

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:

  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:

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:

  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:

  • 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 description zu 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.