MCP Server in TypeScript bauen: Vollstaendige Anleitung mit Claude

April 21, 2026 · 14 min read · mcp, typescript, claude, llm-tools, anthropic
MCP Server in TypeScript bauen: Vollstaendige Anleitung mit Claude

Die meisten Teams brauchen keinen eigenen MCP Server. Wenn Sie eine LLM-App, eine Integration und eine Codebase haben, ist der direkte Aufruf der Anbieter-API schneller fertig und leichter zu debuggen. Sobald Sie aber zwei Claude-Oberflaechen (Claude Desktop plus Claude Code, oder Claude Code plus Cursor) haben, die auf dasselbe interne System zugreifen, hoeren Sie auf, Tool-Code zu duplizieren. Ab diesem Punkt lohnt es sich, einen MCP Server in TypeScript zu bauen.

Das hier ist ein durchgehender Build. Sie setzen einen Node.js-Server mit dem offiziellen SDK auf, binden stdio-Transport an, validieren Eingaben mit zod, behandeln Fehler so, wie das Protokoll es erwartet, verbinden ihn mit Claude Desktop und Claude Code, debuggen mit dem MCP Inspector und betreiben ihn in Produktion unter systemd. Der Beispielserver cached abgerufene URLs und stellt vier Tools bereit. Ich betreibe einen davon produktiv als Wrapper um die TickTick-API, die folgenden Muster sind also die Muster, die ich einsetze, wenn etwas jeden Morgen um 6:30 Uhr den Cron-Loop ueberleben muss.

Kein Fluff, kein Spielcode. Kopieren, bauen, laufen lassen.

Was ein MCP Server tatsaechlich ist

MCP ist das Model Context Protocol, Anthropics offene Spezifikation dafuer, wie ein LLM-Client mit externen Faehigkeiten spricht. Ein MCP Server stellt einem Client drei Dinge zur Verfuegung: Tools (Funktionen, die das Modell aufrufen kann), Resources (Dokumente oder Daten, die das Modell lesen kann) und Prompts (Template-Nachrichtenfluesse, die der Nutzer ausloesen kann). Die Transport-Schicht ist entweder stdio (ein lokaler Kindprozess, der JSON-RPC ueber stdin/stdout spricht) oder SSE/HTTP (ein entfernter Server, der JSON-RPC ueber Server-Sent Events spricht).

Das Modell sieht Ihren Code nicht. Es sieht eine Liste von Tool-Namen mit Beschreibungen und JSON-Schema-Eingabedefinitionen. Wenn es einen Aufruf beschliesst, leitet der Client den Aufruf an Ihren Server weiter, Ihr Server fuehrt den Handler aus und Sie liefern ein Ergebnis zurueck. Das war’s. Das Protokoll ist klein, das SDK ist schlank und die gesamte Sache passt in einen einzigen Prozess.

Einen tieferen konzeptionellen Ueberblick finden Sie unter MCP servers explained. Fuer den Entscheidungsbaum, ob Sie ueberhaupt einen brauchen, deckt MCP vs custom API integration die Abwaegungen ab.

Wann der Eigenbau einen direkten API-Aufruf schlaegt

Hier ist meine Regel. Bauen Sie einen MCP Server, wenn mindestens zwei der folgenden Punkte zutreffen:

  1. Sie nutzen Claude Code oder Claude Desktop intensiv und wollen, dass der Assistent aus jeder Session heraus dasselbe interne System (Ihren Issue-Tracker, Ihre Deploy-Pipeline, Ihre Kunden-DB) erreicht, ohne API-Keys kopieren zu muessen.
  2. Mehrere LLM-Clients brauchen dieselbe Faehigkeit. Claude Desktop fuer spontane Abfragen, Claude Code fuer Coding, vielleicht Cursor fuer einen Kollegen. Ohne MCP schreiben Sie den Tool-Handler dreimal.
  3. Die Integration ist langlebig. Wenn sie in sechs Monaten noch existiert, amortisiert sich der MCP-Wrapper durch weniger Duplikation.
  4. Sie wollen rein lokale Ausfuehrung. stdio-Transport laesst Ihren Server als Kindprozess des Clients laufen, mit Zugriff auf lokale Dateien und lokale Netzwerke. Kein Webhook, keine oeffentliche URL, keine Auth-Schicht zu bauen.

Verzichten Sie darauf, wenn Sie ein einziges Python-Skript haben, das die Claude API mit inline definierten Tool-Definitionen aufruft. Das in MCP zu verpacken, fuegt eine Prozessgrenze und eine Protokollschicht ohne Nutzen hinzu. Claude API tool use deckt das Muster des direkten Aufrufs ab, das fuer Einzelanwendungsfaelle weiterhin der richtige Standard ist.

Gegenbeispiel aus meinem eigenen Stack: Ich betreibe einen TickTick MCP Server als systemd-Service, weil dieselbe Tool-Oberflaeche von Claude Code (fuer die taegliche Aufgaben-Triage), von einem CLI namens tt (fuer Shell-Automatisierung) und von einem Telegram-Bot (fuer Mobile Capture) genutzt wird. Drei Clients, ein Server, eine Tool-Definition pro Aktion. Einmal bauen, ueberall anbinden.

Einen TypeScript-Server aufsetzen

Erstellen Sie ein neues Projekt. Ich verwende Node 20 LTS und TypeScript 5.4.

mkdir url-cache-mcp && cd url-cache-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init

Editieren Sie package.json. Fuegen Sie den Module-Type, den Bin-Eintrag und die Skripte hinzu.

{
  "name": "url-cache-mcp",
  "version": "0.1.0",
  "type": "module",
  "bin": {
    "url-cache-mcp": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/node": "^20.11.0",
    "tsx": "^4.7.0",
    "typescript": "^5.4.0"
  }
}

Jetzt tsconfig.json. Node16 Module Resolution ist der entscheidende Punkt, das SDK verwendet explizite .js-Endungen in seiner Exports-Map.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": false,
    "sourceMap": true
  },
  "include": ["src/**/*"]
}

Erstellen Sie src/index.ts mit einem minimalen Server, der startet, sich ankuendigt und bei SIGINT sauber beendet. Fuegen Sie den Shebang hinzu, damit die kompilierte Ausgabe als Binary laufen kann.

#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "url-cache-mcp",
    version: "0.1.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("url-cache-mcp ready on stdio");
}

main().catch((err) => {
  console.error("fatal:", err);
  process.exit(1);
});

Zwei Dinge sollten Sie beachten. Erstens gehen Logs nach stderr, niemals nach stdout. Stdout ist der Protokollkanal. Alles, was Sie dort hineinschreiben, korrumpiert den JSON-RPC-Stream, und Claude Desktop verwirft Ihren Server stillschweigend. Zweitens deklariert das capabilities-Objekt, was dieser Server anbietet. Nur Tools ist der uebliche Fall.

Fuehren Sie npm run build && node dist/index.js aus. Er haengt auf der Warteschleife nach einem Client auf stdin. Das ist korrekt. Beenden Sie ihn mit Strg-C.

Das erste Tool schreiben

Der Beispielserver cached URL-Abrufe. Vier Tools: fetch_url, list_cache, clear_cache, search_cache. Ich gehe das erste komplett durch und zeige den Rest danach kompakt.

Tool-Definitionen finden an zwei Stellen statt. Sie bewerben das Tool-Schema in ListTools und fuehren den Handler in CallTool aus. Ich definiere Schemas mit zod und konvertiere sie zur Bewerbungszeit in JSON Schema. Damit erhalten Sie die Laufzeit-Validierung auf Handler-Seite gratis.

Fuegen Sie ein Cache-Modul unter src/cache.ts hinzu.

type CacheEntry = {
  url: string;
  content: string;
  fetchedAt: number;
  bytes: number;
};

const cache = new Map<string, CacheEntry>();

export function put(url: string, content: string): CacheEntry {
  const entry: CacheEntry = {
    url,
    content,
    fetchedAt: Date.now(),
    bytes: Buffer.byteLength(content, "utf8"),
  };
  cache.set(url, entry);
  return entry;
}

export function get(url: string): CacheEntry | undefined {
  return cache.get(url);
}

export function list(): CacheEntry[] {
  return Array.from(cache.values()).sort(
    (a, b) => b.fetchedAt - a.fetchedAt
  );
}

export function remove(url: string): boolean {
  return cache.delete(url);
}

export function clearAll(): number {
  const n = cache.size;
  cache.clear();
  return n;
}

export function search(query: string): CacheEntry[] {
  const q = query.toLowerCase();
  return list().filter(
    (e) =>
      e.url.toLowerCase().includes(q) ||
      e.content.toLowerCase().includes(q)
  );
}

Jetzt die Tool-Schemas. Fuegen Sie src/tools.ts hinzu.

import { z } from "zod";

export const FetchUrlInput = z.object({
  url: z.string().url().describe("Absolute HTTP or HTTPS URL to fetch"),
  force: z
    .boolean()
    .optional()
    .describe("Bypass cache and refetch (default false)"),
});

export const ListCacheInput = z.object({});

export const ClearCacheInput = z.object({
  url: z
    .string()
    .url()
    .optional()
    .describe("Clear only this URL. Omit to clear everything."),
});

export const SearchCacheInput = z.object({
  query: z.string().min(1).describe("Substring match on URL or page text"),
});

Zod-Schemas sind fuer die Laufzeit grossartig, aber das MCP-Protokoll erwartet JSON Schema in der Tool-Bewerbung. Fuegen Sie einen kleinen Converter-Helfer hinzu. Sie koennen zod-to-json-schema verwenden, wenn Sie sich das sparen wollen, aber die handgerollten Shapes unten sind fuer ein Tutorial klarer.

export const toolDefinitions = [
  {
    name: "fetch_url",
    description:
      "Fetch the text content of a URL. Results are cached in-process. Returns the page text, content type, and whether the result came from cache.",
    inputSchema: {
      type: "object",
      properties: {
        url: {
          type: "string",
          format: "uri",
          description: "Absolute HTTP or HTTPS URL to fetch",
        },
        force: {
          type: "boolean",
          description: "Bypass cache and refetch (default false)",
        },
      },
      required: ["url"],
    },
  },
  {
    name: "list_cache",
    description: "List all cached URLs with fetch timestamp and byte size.",
    inputSchema: { type: "object", properties: {} },
  },
  {
    name: "clear_cache",
    description:
      "Clear one cached URL, or all cached URLs if no URL is given.",
    inputSchema: {
      type: "object",
      properties: {
        url: {
          type: "string",
          format: "uri",
          description: "URL to clear. Omit to clear everything.",
        },
      },
    },
  },
  {
    name: "search_cache",
    description:
      "Substring search across cached URLs and page text. Case insensitive.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Substring to match on URL or content",
        },
      },
      required: ["query"],
    },
  },
];

Verdrahten Sie jetzt die Handler in src/index.ts. Ersetzen Sie den Dateiinhalt hierdurch.

#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import * as cache from "./cache.js";
import {
  ClearCacheInput,
  FetchUrlInput,
  ListCacheInput,
  SearchCacheInput,
  toolDefinitions,
} from "./tools.js";

const server = new Server(
  { name: "url-cache-mcp", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: toolDefinitions,
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case "fetch_url": {
        const input = FetchUrlInput.parse(args);
        const cached = cache.get(input.url);
        if (cached && !input.force) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(
                  {
                    url: cached.url,
                    bytes: cached.bytes,
                    fromCache: true,
                    content: cached.content,
                  },
                  null,
                  2
                ),
              },
            ],
          };
        }
        const res = await fetch(input.url, {
          headers: { "User-Agent": "url-cache-mcp/0.1" },
        });
        if (!res.ok) {
          throw new McpError(
            ErrorCode.InternalError,
            `HTTP ${res.status} ${res.statusText} for ${input.url}`
          );
        }
        const text = await res.text();
        const entry = cache.put(input.url, text);
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  url: entry.url,
                  bytes: entry.bytes,
                  fromCache: false,
                  content: entry.content,
                },
                null,
                2
              ),
            },
          ],
        };
      }

      case "list_cache": {
        ListCacheInput.parse(args ?? {});
        const items = cache.list().map((e) => ({
          url: e.url,
          bytes: e.bytes,
          fetchedAt: new Date(e.fetchedAt).toISOString(),
        }));
        return {
          content: [
            { type: "text", text: JSON.stringify(items, null, 2) },
          ],
        };
      }

      case "clear_cache": {
        const input = ClearCacheInput.parse(args ?? {});
        if (input.url) {
          const ok = cache.remove(input.url);
          return {
            content: [
              {
                type: "text",
                text: ok
                  ? `Cleared ${input.url}`
                  : `Not in cache: ${input.url}`,
              },
            ],
          };
        }
        const n = cache.clearAll();
        return {
          content: [{ type: "text", text: `Cleared ${n} entries` }],
        };
      }

      case "search_cache": {
        const input = SearchCacheInput.parse(args);
        const hits = cache.search(input.query).map((e) => ({
          url: e.url,
          bytes: e.bytes,
          snippet: snippet(e.content, input.query),
        }));
        return {
          content: [
            { type: "text", text: JSON.stringify(hits, null, 2) },
          ],
        };
      }

      default:
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Unknown tool: ${name}`
        );
    }
  } catch (err) {
    if (err instanceof McpError) throw err;
    if (err instanceof Error) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `${name} failed: ${err.message}`
      );
    }
    throw new McpError(ErrorCode.InternalError, `${name} failed`);
  }
});

function snippet(text: string, query: string, radius = 80): string {
  const idx = text.toLowerCase().indexOf(query.toLowerCase());
  if (idx === -1) return "";
  const start = Math.max(0, idx - radius);
  const end = Math.min(text.length, idx + query.length + radius);
  return (start > 0 ? "..." : "") + text.slice(start, end) + (end < text.length ? "..." : "");
}

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("url-cache-mcp ready on stdio");
}

main().catch((err) => {
  console.error("fatal:", err);
  process.exit(1);
});

Bauen Sie mit npm run build. Sie haben jetzt einen funktionierenden MCP Server unter dist/index.js.

Fehlerbehandlung und Validierung

Das Muster oben ist das, das Sie kopieren sollten. Drei Regeln.

Werfen Sie McpError, nicht plain Error. Das SDK serialisiert McpError in eine ordentliche JSON-RPC-Fehlerantwort. Ein plain throw wird aus Sicht des Clients zu einem generischen Server-Crash, und Claude sieht nichts Nuetzliches.

Parsen Sie mit zod an der Handler-Grenze. Zod faengt Schema-Drift ab, konvertiert Typen und gibt Ihnen nach .parse() typisierte Eingaben. Wenn zod einen Fehler wirft, konvertiert der aeussere catch-Block ihn zu InvalidParams, was das Modell erwartet, wenn es falsche Argumente gesendet hat.

Verschlucken Sie Fehler niemals stillschweigend. Geben Sie sie zurueck. Das Modell kann problemlos “HTTP 404 for https://example.com/missing" lesen und erneut versuchen oder aufgeben. Den Fehler zu schlucken und ein leeres Ergebnis zurueckzugeben, lehrt das Modell, dass Ihr Tool unzuverlaessig ist.

Ein zusaetzliches Muster, das ich in Produktion verwende: Wenn ein Tool teilweise erfolgreich sein kann, geben Sie eine strukturierte Antwort mit sowohl dem Erfolgs-Payload als auch etwaigen Warnungen zurueck, anstatt zu werfen. Das Modell argumentiert besser mit strukturiertem Zustand als mit binaerem Erfolg/Misserfolg.

Lokale Ausfuehrung mit Claude Desktop und Claude Code

Fuer Claude Desktop editieren Sie unter macOS ~/Library/Application Support/Claude/claude_desktop_config.json oder das Aequivalent unter Windows. Fuegen Sie einen mcpServers-Eintrag hinzu.

{
  "mcpServers": {
    "url-cache": {
      "command": "node",
      "args": ["/absolute/path/to/url-cache-mcp/dist/index.js"]
    }
  }
}

Starten Sie Claude Desktop neu. Die Tools erscheinen im Tools-Menue. Bitten Sie es, eine URL abzurufen, und Sie sehen den Aufruf.

Fuer Claude Code editieren Sie ~/.claude.json. Gleiche Struktur.

{
  "mcpServers": {
    "url-cache": {
      "command": "node",
      "args": ["/absolute/path/to/url-cache-mcp/dist/index.js"]
    }
  }
}

Oder nutzen Sie das CLI: claude mcp add url-cache node /absolute/path/to/dist/index.js.

Claude Code laedt die MCP-Konfiguration beim Session-Start neu. Oeffnen Sie eine neue Session und die Tools stehen bereit. In einer Claude-Code-Session ruft das Modell fetch_url autonom auf, wenn Sie es um die Zusammenfassung einer Webseite bitten. Genau darum geht es beim Claude-MCP-Integrationspfad, Sie hoeren auf, URLs in den Context zu kopieren, und lassen das Modell sie holen.

Fuer ein vollstaendigeres Bild von Agent-Patterns oberhalb von MCP, siehe Claude Code SDK agents.

Produktivbetrieb

Lokales stdio reicht fuer 90 Prozent der Einzelnutzer-Faelle. Wenn Sie etwas brauchen, das durchlaeuft, mehrere Clients bedient oder Neustarts ueberlebt, verschieben Sie es unter einen Prozessmanager. Ich betreibe meinen TickTick MCP Server als systemd-Service auf einem kleinen Debian-VPS. Die Unit-Datei hat 15 Zeilen, der Server laeuft seit Monaten und ein Neustart nach einer Code-Aenderung ist ein einziger Befehl.

Eine minimale Unit-Datei unter /etc/systemd/system/url-cache-mcp.service:

[Unit]
Description=URL Cache MCP Server
After=network.target

[Service]
Type=simple
User=debian
WorkingDirectory=/home/user/url-cache-mcp
ExecStart=/usr/bin/node /home/user/url-cache-mcp/dist/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Dann sudo systemctl daemon-reload && sudo systemctl enable --now url-cache-mcp. Logs verfolgen: journalctl -u url-cache-mcp -f.

Ein Vorbehalt. stdio-Transport laeuft als Kind des Clients, ein per systemd verwalteter stdio-Server ist also nicht direkt nuetzlich, solange Sie ihm nicht etwas vorschalten, das stdio-Verbindungen multiplext. In der Praxis bleiben Produktiv-MCP-Server entweder als lokal pro Session gespawnte stdio-Prozesse bestehen, oder sie laufen als SSE/HTTP-Server hinter Auth. Der SSE-Transport ist im SDK dokumentiert und folgt demselben Handler-Muster wie oben gezeigt, nur die Transport-Zeile aendert sich.

Debuggen mit dem MCP Inspector

Das mit Abstand beste Debug-Werkzeug ist der Inspector. Fuehren Sie ihn gegen Ihren gebauten Server aus.

npx @modelcontextprotocol/inspector node dist/index.js

Das oeffnet eine lokale Web-Oberflaeche. Sie sehen jedes Tool, das Ihr Server bewirbt, koennen sie manuell mit beliebigen JSON-Argumenten aufrufen und die Request/Response-Frames in Echtzeit beobachten. Jedes Mal, wenn ein Tool in Claude Desktop Aerger macht, schalte ich zuerst den Inspector davor. Das Problem ist fast immer ein Schema-Mismatch oder eine Log-Zeile, die nach stdout geht.

Fuer Integrationstests aus einem Skript koennen Sie den Server auch ueber SSE JSON-RPC mit supergateway als stdio-zu-SSE-Bruecke aufrufen. Ich verwende dieses Muster, um meinen TickTick MCP Server aus Shell-Skripten heraus anzusprechen, ohne jedes Mal einen vollen Node-Kindprozess zu spawnen. Der Punkt bleibt derselbe, das Protokoll ist einfach JSON-RPC, also funktioniert jeder Client, der es spricht.

Sicherheit

Eine Sache, die Teams uebersehen. Ein MCP Server laeuft mit den vollen Rechten des Nutzers, der ihn gestartet hat. Wenn Claude Desktop unter Ihrem Nutzer laeuft, kann Ihr MCP Server ~/.ssh lesen, Dateien in Ihrem Home-Verzeichnis loeschen und jede interne URL erreichen, die Ihre Maschine erreichen kann. Das Modell entscheidet, wann es ein Tool aufruft, und eine Prompt Injection in einer abgerufenen Webseite kann es durchaus ueberzeugen, eines mit feindseligen Argumenten aufzurufen.

Drei Verteidigungslinien, die sich vom ersten Tag an lohnen.

  1. Validieren Sie jede Eingabe strikt. Zod ist nicht optional. URL-Schemes, Pfad-Whitelists, Argumentlaengen-Limits. Ein fetch_url, das file:///etc/passwd akzeptiert, ist ein Bug.
  2. Setzen Sie Rate Limit und Ressourcengrenzen. Ein Modell in einer Schleife kann fetch_url 200 Mal pro Minute aufrufen. Fuegen Sie einen Zaehler pro Tool hinzu und geben Sie jenseits der Schwelle einen Fehler zurueck.
  3. Exponieren Sie niemals einen entfernten MCP Server ohne Auth. SSE-Transport ohne Bearer Token ist eine Hintertuer. Wenn Sie remote ausliefern muessen, stellen Sie einen Reverse Proxy mit HTTP-Auth davor und rotieren Sie Tokens.

Der sicherere Standard fuer die meisten Teams ist stdio-only und lokal-only, wobei externe API-Keys aus Umgebungsvariablen geladen werden, die der Server beim Start liest. Das ist die Haltung, in der ich arbeite.

Bauen oder wrappen?

Hier die Bilanz, ohne Umschweife.

Bauen Sie einen MCP Server, wenn: Sie Claude Code oder Claude Desktop taeglich nutzen, Sie mehr als einen LLM-Client haben, der auf dasselbe interne System zugreift, die Integration langlebig ist (sechs Monate oder mehr) und die gekapselte API etwas ist, das Sie kontrollieren oder dem Sie vertrauen.

Verzichten Sie darauf und rufen Sie die API direkt auf, wenn: Sie ein Skript, einen Workflow, einen LLM-Einstiegspunkt haben. Einen Einmalaufruf in MCP zu verpacken, fuegt eine Prozessgrenze und eine Protokollschicht ohne Nutzen hinzu. Claude API tool use zeigt das direkte Muster.

Verwenden Sie den MCP Server eines anderen, wenn: bereits einer fuer das System existiert, das Sie integrieren wollen (GitHub, Postgres, Filesystem, Slack). Das Oekosystem ist schnell gewachsen, und einen veroeffentlichten Server umzuschreiben, um 50 Zeilen einzusparen, lohnt sich selten im Verhaeltnis zum Wartungsaufwand.

Ueberlegen Sie genau, wenn: Sie abwaegen, ob MCP ueberhaupt die richtige Abstraktion ist. Fuer manche internen Integrationen reicht eine kleine REST-API oder eine Claude-API-native Tool-Definition. Die Build-vs-Buy-Rechnung ist hier wichtig und ich behandle sie in AI agents build vs buy.

Der grosse Teil des Wertes von MCP liegt nicht im Protokoll selbst, sondern in der Disziplin, Ihre Tools einmal, sauber, mit echten Schemas und echten Fehlertypen zu definieren. Selbst wenn Sie nie einen zweiten Client anschliessen, fuehrt diese Uebung in der Regel zu besserem Integrationscode als das inline tools=[...]-Array, das Sie sonst geschrieben haetten.

Das obige Tutorial liefert Ihnen einen lauffaehigen Server in unter 200 Zeilen. Wenn Sie eine Sache damit bauen, bauen Sie einen Wrapper um das eine interne System, das Sie am haeufigsten in Claude einfuegen. Sie werden ihn jeden Tag benutzen.

Weiterfuehrende Artikel

Download the AI Automation Checklist (PDF)