Plugin Architecture 9 min read

MCP-Server-Integrations-Leitfaden für Claude Code

Wie Sie Model Context Protocol Server entwickeln und integrieren, die Claude Code mit Ihren internen Systemen, Datenbanken und APIs verbinden.

By Julian Pechler |

MCP verstehen

Model Context Protocol (MCP) ist ein offener Standard, der KI-Assistenten wie Claude Code ermöglicht, auf strukturierte, sichere Weise mit externen Systemen zu interagieren. Stellen Sie sich MCP als die Brücke zwischen Claudes Intelligenz und den Daten und Tools Ihrer Organisation vor.

Ohne MCP operiert Claude Code mit dem Kontext, den Sie in die Konversation einfügen. Sie kopieren Code-Snippets, beschreiben Ihre Architektur, erklären Ihre Systeme. Es funktioniert, aber es ist manuell und begrenzt.

Mit MCP kann Claude Code:

  • Ihre interne Dokumentationsdatenbank direkt abfragen
  • Ihre Codebase-Struktur und -Muster lesen
  • Auf Ihre API-Spezifikationen zugreifen
  • Informationen in Ihrer Wissensdatenbank nachschlagen
  • Genehmigte Operationen in Ihren Systemen ausführen

Die Schlüsselerkenntnis: MCP-Server geben Claude Code keinen direkten Zugang zu Systemen. Sie bieten ein kuratiertes, kontrolliertes Interface. Sie entscheiden genau, welche Fähigkeiten exponiert werden und wie.

MCP-Architektur

Die MCP-Architektur hat drei Komponenten:

Clients (Claude Code): Fordern Tools und Ressourcen von Servern an. Claude Code ist ein MCP-Client—es entdeckt, was Server anbieten, und nutzt diese Fähigkeiten.

Server: Exponieren spezifische Tools und Ressourcen. Ein Server könnte Zugang zu Ihrer Dokumentation, Ihren API-Specs, Ihrer Codebase-Struktur oder spezifischen Operationen bieten.

Transport: Die Kommunikationsschicht zwischen Clients und Servern. MCP unterstützt stdio (lokal) und HTTP/SSE (remote) Transports.

┌─────────────┐     MCP Protocol     ┌─────────────┐     Internal     ┌─────────────┐
│             │ ◄──────────────────► │             │ ◄──────────────► │             │
│ Claude Code │      (stdio/HTTP)    │ MCP Server  │   (API/SQL/etc)  │  Ihre       │
│   (Client)  │                      │             │                  │  Systeme    │
└─────────────┘                      └─────────────┘                  └─────────────┘

Tools vs. Ressourcen

MCP-Server exponieren zwei Arten von Fähigkeiten:

Tools sind Aktionen, die Claude Code ausführen kann. Sie sind wie Funktionen:

// Beispiel: Ein Tool zur Dokumentationssuche
tool: search_docs
inputs:
  query: string  // Suchanfrage
  limit: number  // Max Ergebnisse
output:
  results: Array<{title, excerpt, url}>

Claude Code entscheidet basierend auf der Konversation, wann Tools genutzt werden. “Finde Dokumentation über Authentifizierung” würde das search_docs Tool auslösen.

Ressourcen sind Daten, auf die Claude Code zugreifen kann. Sie sind wie Dateien oder Datenbanken:

// Beispiel: Eine Ressource mit API-Specs
resource: api_spec
uri: api://specs/users
output:
  openapi: string  // OpenAPI-Spezifikation

Ressourcen bieten Kontext. Tools ermöglichen Aktionen. Die nützlichsten MCP-Server bieten beides.

MCP-Server entwickeln

Lassen Sie uns einen praktischen MCP-Server bauen. Wir erstellen einen Dokumentations-Server, der Claude Code Zugang zu Ihren internen Docs gibt.

Server-Struktur

Ein minimaler MCP-Server in TypeScript:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server(
  {
    name: 'docs-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
      resources: {},
    },
  }
);

// Tools definieren
server.setRequestHandler('tools/list', async () => ({
  tools: [
    {
      name: 'search_docs',
      description: 'Durchsuche interne Dokumentation',
      inputSchema: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Suchanfrage' },
          limit: { type: 'number', default: 10 },
        },
        required: ['query'],
      },
    },
  ],
}));

// Tool-Aufrufe behandeln
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'search_docs') {
    const results = await searchDocumentation(args.query, args.limit);
    return { content: [{ type: 'text', text: JSON.stringify(results) }] };
  }

  throw new Error(`Unbekanntes Tool: ${name}`);
});

// Ressourcen definieren
server.setRequestHandler('resources/list', async () => ({
  resources: [
    {
      uri: 'docs://architecture/overview',
      name: 'Architektur-Übersicht',
      description: 'System-Architektur-Dokumentation',
      mimeType: 'text/markdown',
    },
  ],
}));

// Ressourcen-Lesevorgänge behandeln
server.setRequestHandler('resources/read', async (request) => {
  const { uri } = request.params;

  if (uri === 'docs://architecture/overview') {
    const content = await readDocFile('architecture/overview.md');
    return { contents: [{ uri, text: content, mimeType: 'text/markdown' }] };
  }

  throw new Error(`Unbekannte Ressource: ${uri}`);
});

// Server starten
const transport = new StdioServerTransport();
await server.connect(transport);

Integrationsmuster

Verschiedene Systeme erfordern verschiedene Integrationsansätze.

Datenbank-Integration

Claude Code mit Ihren internen Datenbanken für Abfragen (nicht Modifikationen) verbinden:

// Datenbank-Server-Beispiel
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'query_users') {
    // Parametrisierte Abfragen verwenden um Injection zu verhindern
    const result = await db.query(
      'SELECT id, name, email FROM users WHERE department = $1 LIMIT $2',
      [args.department, args.limit || 10]
    );

    return {
      content: [{
        type: 'text',
        text: JSON.stringify(result.rows)
      }]
    };
  }
});

API-Integration

Ihre internen APIs durch MCP exponieren:

// API-Gateway-Server
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'get_user_details') {
    const response = await fetch(
      `${INTERNAL_API}/users/${args.userId}`,
      {
        headers: {
          'Authorization': `Bearer ${SERVICE_TOKEN}`,
          'X-Request-Source': 'mcp-server',
        },
      }
    );

    if (!response.ok) {
      throw new Error(`API-Fehler: ${response.status}`);
    }

    const data = await response.json();

    // Sensible Felder vor Rückgabe filtern
    const safeData = {
      id: data.id,
      name: data.name,
      department: data.department,
      // Ausschließen: email, phone, address, etc.
    };

    return {
      content: [{
        type: 'text',
        text: JSON.stringify(safeData)
      }]
    };
  }
});

Dateisystem-Integration

Zugang zu bestimmten Verzeichnissen bereitstellen:

// Code-Kontext-Server
server.setRequestHandler('resources/list', async () => {
  const files = await glob('src/**/*.ts');

  return {
    resources: files.map(file => ({
      uri: `file://${file}`,
      name: path.basename(file),
      description: `Quelldatei: ${file}`,
      mimeType: 'text/typescript',
    })),
  };
});

server.setRequestHandler('resources/read', async (request) => {
  const { uri } = request.params;

  // Sicherheit: Pfad validieren ob innerhalb erlaubtem Verzeichnis
  const filePath = uri.replace('file://', '');
  const resolvedPath = path.resolve(filePath);

  if (!resolvedPath.startsWith(ALLOWED_DIR)) {
    throw new Error('Zugriff verweigert: Pfad außerhalb erlaubtem Verzeichnis');
  }

  const content = await fs.readFile(resolvedPath, 'utf-8');

  return {
    contents: [{
      uri,
      text: content,
      mimeType: 'text/typescript',
    }],
  };
});

Sicherheitsüberlegungen

MCP-Server können mächtige Angriffsvektoren sein, wenn sie nicht korrekt abgesichert sind. Sicherheit operiert auf mehreren Ebenen.

Authentifizierung

Wer kann sich mit Ihrem MCP-Server verbinden?

Lokale Server (stdio Transport):

  • Prozess-Level-Sicherheit: Nur Claude Code-Prozess kann sich verbinden
  • Benutzer-Level-Zugriff: Erbt Benutzerberechtigungen
  • Niedriges Risiko für schreibgeschützte Operationen

Remote-Server (HTTP Transport):

  • Authentifizierung für alle Verbindungen erforderlich
  • OAuth2 oder JWT-Tokens verwenden
  • Tokens bei jeder Anfrage validieren
// Authentifizierungs-Middleware für HTTP-Transport
async function authenticateRequest(request: Request): Promise<User> {
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');

  if (!token) {
    throw new AuthError('Fehlender Autorisierungstoken');
  }

  try {
    const payload = await verifyJWT(token);
    return await getUser(payload.sub);
  } catch (error) {
    throw new AuthError('Ungültiger Token');
  }
}

Autorisierung

Was können authentifizierte Benutzer tun?

Tool-Level-Autorisierung:

const toolPermissions = {
  'search_docs': ['all_users'],
  'query_database': ['engineers', 'data_team'],
  'modify_settings': ['admins'],
};

server.setRequestHandler('tools/call', async (request) => {
  const user = await authenticateRequest(request);
  const { name } = request.params;

  const allowedRoles = toolPermissions[name] || [];
  if (!allowedRoles.includes('all_users') &&
      !user.roles.some(r => allowedRoles.includes(r))) {
    throw new AuthError(`Benutzer hat keine Berechtigung für Tool: ${name}`);
  }

  // Mit Tool-Ausführung fortfahren
});

Eingabevalidierung

Niemals Eingaben von Claude Code vertrauen:

import { z } from 'zod';

const SearchDocsInput = z.object({
  query: z.string().min(1).max(500),
  limit: z.number().int().min(1).max(100).default(10),
  department: z.enum(['engineering', 'product', 'design']).optional(),
});

server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'search_docs') {
    // Eingaben validieren
    const validatedArgs = SearchDocsInput.parse(args);

    // Validierte Eingaben verwenden
    const results = await searchDocs(validatedArgs);
    return { content: [{ type: 'text', text: JSON.stringify(results) }] };
  }
});

Rate Limiting

Missbrauch verhindern und Backend-Systeme schützen:

import { RateLimiter } from 'limiter';

const limiters = new Map<string, RateLimiter>();

function getRateLimiter(userId: string): RateLimiter {
  if (!limiters.has(userId)) {
    limiters.set(userId, new RateLimiter({
      tokensPerInterval: 100,
      interval: 'minute',
    }));
  }
  return limiters.get(userId)!;
}

server.setRequestHandler('tools/call', async (request) => {
  const user = await authenticateRequest(request);
  const limiter = getRateLimiter(user.id);

  if (!await limiter.removeTokens(1)) {
    throw new RateLimitError('Rate-Limit überschritten');
  }

  // Mit Anfrage fortfahren
});

Audit-Logging

Alle MCP-Server-Aktivitäten protokollieren:

interface AuditLog {
  timestamp: Date;
  userId: string;
  action: 'tool_call' | 'resource_read';
  target: string;
  inputs: object;
  result: 'success' | 'error';
  duration: number;
  errorMessage?: string;
}

async function logAudit(log: AuditLog): Promise<void> {
  await auditDatabase.insert(log);

  // Bei verdächtigen Mustern alarmieren
  if (await detectAnomalous(log)) {
    await alertSecurityTeam(log);
  }
}

Deployment-Muster

Wie Sie MCP-Server deployen beeinflusst Sicherheit, Performance und Wartbarkeit.

Lokales Deployment

Für Entwicklung und persönliche Nutzung:

// Claude Code Konfiguration
{
  "mcpServers": {
    "docs": {
      "command": "node",
      "args": ["/pfad/zu/docs-server/index.js"],
      "env": {
        "DOCS_PATH": "/pfad/zu/docs"
      }
    }
  }
}

Vorteile: Einfach, schnell, kein Netzwerk-Overhead Nachteile: Funktioniert nur lokal, keine zentrale Verwaltung

Containerisiertes Deployment

Für teamweiten Zugang:

FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Kubernetes-Deployment

Für Enterprise-Skalierung:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-docs-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-docs
  template:
    metadata:
      labels:
        app: mcp-docs
    spec:
      containers:
      - name: mcp-docs
        image: company/mcp-docs:1.0.0
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: database-url

Performance-Optimierung

MCP-Server sitzen zwischen Claude Code und Ihren Systemen. Performance ist wichtig.

Connection Pooling

Keine neuen Verbindungen pro Anfrage erstellen:

import { Pool } from 'pg';

// Pool einmal beim Start erstellen
const pool = new Pool({
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Verbindungen für Anfragen wiederverwenden
server.setRequestHandler('tools/call', async (request) => {
  const client = await pool.connect();
  try {
    const result = await client.query('SELECT ...');
    return { content: [{ type: 'text', text: JSON.stringify(result.rows) }] };
  } finally {
    client.release();
  }
});

Caching

Häufig abgerufene Daten cachen:

import { LRUCache } from 'lru-cache';

const cache = new LRUCache<string, any>({
  max: 1000,
  ttl: 1000 * 60 * 5, // 5 Minuten
});

async function getCachedDocs(query: string): Promise<any[]> {
  const cacheKey = `docs:${query}`;

  if (cache.has(cacheKey)) {
    return cache.get(cacheKey);
  }

  const results = await searchDocs(query);
  cache.set(cacheKey, results);

  return results;
}

Timeouts

Vernünftige Timeouts setzen:

const TOOL_TIMEOUT = 30000; // 30 Sekunden

server.setRequestHandler('tools/call', async (request) => {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), TOOL_TIMEOUT);

  try {
    const result = await executeToolWithSignal(
      request,
      controller.signal
    );
    return result;
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Tool-Ausführung Timeout');
    }
    throw error;
  } finally {
    clearTimeout(timeout);
  }
});

Erste Schritte

Bereit, MCP-Server für Ihre Organisation zu entwickeln?

  1. Mit schreibgeschützt beginnen: Zuerst einen Dokumentations- oder Codebase-Server bauen
  2. Authentifizierung implementieren: Auch für interne Server
  3. Logging hinzufügen: Sie brauchen Sichtbarkeit darüber, was abgerufen wird
  4. Gründlich testen: MCP-Server brauchen Tests wie jeder Produktionscode
  5. Vorsichtig deployen: Mit begrenztem Zugang starten, schrittweise erweitern

Für Teams, die strukturierte Anleitung zur MCP-Server-Entwicklung wünschen, erfahren Sie mehr über unsere Beratungsleistungen oder lesen Sie unseren Leitfaden zur Claude Code Plugin-Architektur.


Dieser Artikel ist Teil unserer Plugin-Architektur-Serie. Für Skill-Entwicklung siehe Benutzerdefinierte Claude Code Skills erstellen. Für Sicherheitsüberlegungen lesen Sie KI-Governance und Sicherheit für Teams.

Tags

MCP Model Context Protocol Claude Code Integration API Datenbanken