Verbinden Sie interne Unternehmenssysteme über MCP mit Claude Code — vom generischen Fetch-Server bis zum eigenen MCP Server, mit Deployment-Plattformen, Monitoring, Ticketsystemen und Tool-Design-Prinzipien.
Im letzten Artikel ging es um die Anbindung von Datenbanken. Datenbanken verwenden standardisierte Protokolle, daher ist die Integration relativ unkompliziert. Aber die meisten Teams sind im Alltag auch auf eine Vielzahl interner Systeme angewiesen: Deployment-Plattformen, Monitoring-Dashboards, Ticket-Systeme, interne APIs, zentrale Konfigurationsdienste.
Für diese Systeme gibt es in der Regel keinen fertigen MCP Server, aber sie bieten fast alle eine HTTP-API an. In diesem Artikel zeigen wir, wie man mit MCP diese internen Tools an Claude Code anbindet, damit es direkt Monitoring-Metriken abfragen, Deployment-Status prüfen und Tickets verwalten kann.
Es gibt zwei Ansätze, um interne Tools zu integrieren:
Ansatz 1: Einen generischen HTTP-MCP-Server verwenden
In der Community gibt es generische MCP Server, die beliebige REST-APIs als MCP-Tools bereitstellen können. Man erstellt eine API-Beschreibungsdatei, und der Server wandelt sie in Tools um, die Claude aufrufen kann. Geeignet für APIs mit einfacher Struktur, die keine komplexe Logik erfordern.
Ansatz 2: Einen eigenen MCP Server schreiben
Mit dem MCP SDK für TypeScript oder Python einen dedizierten Server entwickeln, mit voller Kontrolle über Tool-Definitionen, Parameter-Validierung und Fehlerbehandlung. Geeignet, wenn man mehrere APIs kombinieren, Daten transformieren oder Geschäftslogik einbauen muss.
Dieser Artikel behandelt beide Ansätze, angefangen mit dem einfacheren.
Die leichtgewichtigste Lösung ist das offizielle Paket @anthropic-ai/mcp-server-fetch, das Claude ermöglicht, direkt HTTP-Anfragen zu senden. Die Konfiguration ist minimal:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-fetch"]
}
}
}
Nach der Konfiguration kann Claude direkt interne APIs aufrufen:
Prüfe den aktuellen Status des Services auf der Deployment-Plattform https://deploy.internal.com/api/v1/services/user-service
Claude sendet eine GET-Anfrage, empfängt die Antwort und bereitet sie verständlich auf.
Dieser Ansatz hat jedoch klare Einschränkungen:
Taugt für den Einzelfall, aber nicht als dauerhafte Lösung.
Wenn man ein internes System regelmäßig nutzt, ist ein dedizierter MCP Server die bessere Wahl. Im Folgenden zeigen wir das an einem konkreten Beispiel: die Integration einer unternehmensinternen Deployment-Plattform.
Angenommen, die Deployment-Plattform stellt folgende APIs bereit:
GET /api/v1/services — alle Services auflistenGET /api/v1/services/:name/status — Status eines Services abfragenPOST /api/v1/services/:name/deploy — Deployment auslösenGET /api/v1/services/:name/logs — aktuelle Deployment-Logs anzeigenZunächst das Projekt initialisieren:
mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
Hauptcode in src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = process.env.DEPLOY_API_URL!;
const API_TOKEN = process.env.DEPLOY_API_TOKEN!;
async function api(path: string, method = "GET", body?: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
throw new Error(`API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "deploy-platform",
version: "1.0.0",
});
// Alle Services auflisten
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
const data = await api("/api/v1/services");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// Status eines einzelnen Services abfragen
server.tool(
"service_status",
"查看指定服务的当前部署状态、版本号和健康检查结果",
{ name: z.string().describe("服务名称,如 user-service") },
async ({ name }) => {
const data = await api(`/api/v1/services/${name}/status`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// Deployment-Logs anzeigen
server.tool(
"deploy_logs",
"查看指定服务最近的部署日志",
{
name: z.string().describe("服务名称"),
limit: z.number().optional().default(10).describe("返回条数,默认 10"),
},
async ({ name, limit }) => {
const data = await api(`/api/v1/services/${name}/logs?limit=${limit}`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// Deployment auslösen
server.tool(
"trigger_deploy",
"触发指定服务的部署。这是一个写操作,会实际影响生产环境",
{
name: z.string().describe("服务名称"),
version: z.string().describe("要部署的版本号或 git ref"),
},
async ({ name, version }) => {
const data = await api(`/api/v1/services/${name}/deploy`, "POST", {
version,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
const transport = new StdioServerTransport();
server.connect(transport);
Kompilieren:
npx tsc
{
"mcpServers": {
"deploy": {
"command": "node",
"args": ["/path/to/mcp-deploy/dist/index.js"],
"env": {
"DEPLOY_API_URL": "https://deploy.internal.com",
"DEPLOY_API_TOKEN": "your-api-token"
}
}
}
}
Den Token in .claude/settings.local.json ablegen (nicht in Git committen), die URL in .claude/settings.json (committet, im Team geteilt).
Nach der Konfiguration wird die Konversation ganz natürlich:
Wie ist der aktuelle Status von user-service?
→ Claude ruft service_status("user-service") auf
→ Antwort: läuft, Version v2.3.1, letztes Deployment vor 2 Stunden, alle Health Checks bestanden
Sind kürzlich Deployments fehlgeschlagen?
→ Claude ruft deploy_logs("user-service", 20) auf
→ Analysiert die Logs und teilt mit, dass das dritte Deployment wegen eines Health-Check-Timeouts zurückgerollt wurde
Deploye user-service auf Version v2.3.2
→ Claude ruft trigger_deploy("user-service", "v2.3.2") auf
→ Da die Tool-Beschreibung „Schreiboperation" vermerkt, fragt Claude vorher um Bestätigung
Diese Frage verdient sorgfältige Überlegung.
Leseoperationen können bedenkenlos integriert werden. Status abfragen, Logs lesen, Tickets suchen — diese Operationen haben keine Seiteneffekte; wenn Claude sich irrt, entsteht kein Schaden.
Bei Schreiboperationen gibt es zwei Kategorien:
Schreiboperationen mit geringem Risiko können integriert werden, sollten aber in der Tool-Beschreibung klar gekennzeichnet sein. Claude fordert bei Operationen mit markierten Seiteneffekten automatisch eine Bestätigung an. Beispiele: Tickets erstellen, Nachrichten senden, Konfigurationen aktualisieren.
Schreiboperationen mit hohem Risiko sollte man besser nicht integrieren. Ressourcen löschen, Rollbacks auslösen, Berechtigungen ändern — die Folgen sind schwerwiegend und irreversibel; es ist sicherer, sie manuell durchzuführen.
Wenn man dennoch Schreiboperationen integrieren möchte, sollte man mindestens zwei Dinge tun:
| System | Bereitgestellte Tools | Hinweise |
|---|---|---|
| Deployment-Plattform (K8s / Kamal) | Service-Status abfragen, Logs anzeigen, Deployments auslösen | Schreiboperationen bestätigen lassen |
| Monitoring (Grafana / Datadog) | Metriken abfragen, Alarm-Historie anzeigen | Zeitraum der Abfragen begrenzen, um Datenüberlastung zu vermeiden |
| Ticket-System (Jira / Linear) | Tickets suchen, Tickets erstellen, Status aktualisieren | Tickets erstellen ist eine Schreiboperation, aber risikoarm |
| Interne Dokumentation (Notion / Confluence) | Dokumente suchen, Seiteninhalte lesen | Auf Paginierung achten; nicht zu viel auf einmal laden |
| Konfigurationsverwaltung (Consul / etcd) | Konfigurationen lesen, Umgebungsunterschiede vergleichen | Nur Lesezugriff; keine Schreiboperationen integrieren |
| CI/CD (GitHub Actions / Jenkins) | Build-Status anzeigen, Builds auslösen | Builds auslösen gilt als Schreiboperation mit mittlerem Risiko |
MCP-Tools zu entwerfen ist nicht dasselbe wie APIs zu entwerfen. APIs sind für Entwickler gedacht; Tools sind für die KI gedacht. Einige Prinzipien sind dabei besonders wichtig:
Namen sollten sprechend sein
✗ get_svc_stat — Claude errät nicht unbedingt die Bedeutung der Abkürzung
✓ service_status — sofort verständlich
Beschreibungen sind für die KI geschrieben
Die Beschreibung eines Tools ist keine Dokumentation für Menschen, sondern das, worauf Claude sich stützt, um zu entscheiden, wann das Tool aufgerufen werden soll. Die Beschreibung sollte erklären: was das Tool tut, was es zurückgibt und wann es eingesetzt werden soll.
✗ "Service-Status abrufen"
✓ "Den aktuellen Deployment-Status, die Versionsnummer und das Ergebnis der Health Checks eines bestimmten Services abfragen. Verwenden, wenn der Nutzer fragt, ob ein Service korrekt läuft"
Parameter mit zod klar definieren
Parameter mit .describe() versteht Claude korrekt. Ohne Beschreibung kann Claude nur anhand des Namens raten.
Strukturierte Daten zurückgeben
MCP-Tools liefern Text, aber idealerweise formatiertes JSON. Claude verarbeitet strukturierte Daten deutlich präziser als Fließtext.
Die richtige Granularität wählen
Keinen komplexen Ablauf in ein einzelnes Tool packen. Aber auch keine einfache Abfrage in drei Tools aufteilen. Das Prinzip: ein Tool pro eigenständiger, sinnvoller Operation.
Der Code des MCP Servers kann an verschiedenen Stellen liegen:
Im Projekt-Repository (empfohlen für den Einstieg)
your-project/
├── .claude/settings.json
├── mcp-servers/
│ └── deploy/
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
└── ...
Der Vorteil: Code und Konfiguration liegen zusammen; das Team muss nur klonen und Abhängigkeiten installieren, um loslegen zu können.
Eigenes Repository
Wenn der MCP Server projektübergreifend genutzt werden soll, in ein eigenes Repository auslagern und als npm-Paket oder Docker-Image veröffentlichen.
{
"mcpServers": {
"deploy": {
"command": "npx",
"args": ["-y", "@yourcompany/mcp-deploy-server"]
}
}
}
Globale Installation
Für unternehmensweit eingesetzte MCP Server (z. B. für einheitliche Authentifizierung oder zentrale Log-Plattform) global installieren und in ~/.claude/settings.json konfigurieren.
Die häufigsten Probleme bei der Entwicklung eines MCP Servers sind „Claude ruft mein Tool nicht auf" oder „es wird aufgerufen, aber es gibt einen Fehler".
Prüfen, ob der Server gestartet ist
Nach dem Neustart von Claude Code /mcp eingeben, um die Liste der verbundenen MCP Server anzuzeigen. Wenn der eigene Server nicht aufgelistet ist, command und args überprüfen.
Den Server isoliert testen
MCP Server kommunizieren über stdio und können direkt im Terminal getestet werden:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
Wenn die Tool-Liste zurückgegeben wird, funktioniert der Server an sich korrekt.
Claudes Aufruf-Logs prüfen
Claude Code zeigt Ein- und Ausgabe jedes Tool-Aufrufs an. Wenn die Parameter falsch sind, liegt das meist daran, dass die Tool-Beschreibung oder die Parameter-Definition nicht klar genug war und Claude sie falsch interpretiert hat.
Schauen wir uns ein vollständiges Praxisbeispiel an. Angenommen, wir möchten Sentry in Claude Code integrieren, damit es Produktionsfehler direkt abfragen kann.
server.tool(
"search_errors",
"在 Sentry 中搜索最近的错误。用于排查线上问题、查看错误趋势",
{
query: z.string().describe("搜索关键词,如错误信息、函数名"),
hours: z.number().optional().default(24).describe("查看最近多少小时的错误"),
},
async ({ query, hours }) => {
const since = new Date(Date.now() - hours * 3600000).toISOString();
const data = await api(
`/api/0/projects/${ORG}/${PROJECT}/issues/?query=${encodeURIComponent(query)}&start=${since}&sort=date`
);
const summary = data.map((issue: any) => ({
title: issue.title,
count: issue.count,
firstSeen: issue.firstSeen,
lastSeen: issue.lastSeen,
link: issue.permalink,
}));
return {
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
};
}
);
server.tool(
"error_details",
"查看 Sentry 中某个错误的详细信息,包括堆栈和最近一次事件",
{ issueId: z.string().describe("Sentry issue ID") },
async ({ issueId }) => {
const [issue, latest] = await Promise.all([
api(`/api/0/issues/${issueId}/`),
api(`/api/0/issues/${issueId}/events/latest/`),
]);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
title: issue.title,
count: issue.count,
users: issue.userCount,
stacktrace: latest.entries?.find(
(e: any) => e.type === "exception"
),
},
null,
2
),
},
],
};
}
);
Nach der Integration sieht die Fehlersuche in der Produktion so aus:
Gab es in den letzten 4 Stunden neue 500er-Fehler?
→ Claude durchsucht Sentry
→ Findet 3 neue Issues, das schwerwiegendste betrifft 120 Nutzer
→ Ruft automatisch den Stacktrace ab und identifiziert eine Null-Pointer-Exception
→ Lokalisiert die entsprechende Stelle im Code und schlägt einen Fix vor
Von der Problemerkennung bis zur Lokalisierung im Code – der gesamte Prozess findet in einer einzigen Konversation statt.
In diesem Artikel haben wir gezeigt, wie man mit MCP interne Tools anbindet. Die Kernidee: Das interne System hat eine HTTP-API → man schreibt einen MCP Server als Wrapper → Claude kann es direkt nutzen.
Alle Beispiele in diesem Artikel wrappen bestehende APIs – sowohl die Deployment-Plattform als auch Sentry haben bereits eigene Schnittstellen, und der MCP Server dient lediglich als Übersetzungs- und Adaptionsschicht. Im nächsten Artikel geht es um ein anderes Szenario: Was tun, wenn die benötigte Funktionalität über keine vorhandene API verfügt, und wie man einen MCP Server von Grund auf baut – mit eigener Logik, Zustandsverwaltung und komplexen mehrstufigen Interaktionen.