Model Context Protocol (MCP): Die Zukunft der Tool-Nutzung

Foto von Tiago Ferreira auf Unsplash
Stellen Sie sich vor, Ihre KI könnte nicht nur Text generieren, sondern auch Ihre Datenbank abfragen, E-Mails versenden, Code ausführen und mit beliebigen externen Systemen interagieren – alles über ein standardisiertes Protokoll. Genau das ermöglicht das Model Context Protocol (MCP).
In diesem Artikel tauchen wir tief in die Architektur von MCP ein und bauen gemeinsam einen funktionsfähigen MCP-Server, den Sie direkt in Claude Desktop oder anderen MCP-kompatiblen Clients nutzen können.
Was ist das Model Context Protocol?
MCP ist ein offenes Protokoll, das von Anthropic entwickelt wurde, um die Kommunikation zwischen KI-Modellen und externen Datenquellen oder Tools zu standardisieren. Es löst ein fundamentales Problem: Wie können LLMs sicher und konsistent mit der Außenwelt interagieren?
Das Problem vor MCP
Traditionell musste jede Integration individuell entwickelt werden:
Probleme:
- Jedes Tool braucht eigene Integration
- Keine Wiederverwendbarkeit
- Inkonsistente Schnittstellen
- Sicherheitsrisiken durch Ad-hoc-Lösungen
Die MCP-Lösung
MCP führt eine standardisierte Abstraktionsschicht ein:
Architektur im Detail
MCP basiert auf einer Client-Server-Architektur mit JSON-RPC 2.0 als Kommunikationsprotokoll.
Die drei Hauptkomponenten
1. MCP Host (Client)
Der Host ist die KI-Anwendung, die MCP-Server nutzt:
- Claude Desktop: Anthropics Desktop-Anwendung
- Claude Code: Die CLI für Entwickler
- Eigene Anwendungen: Jede App, die das MCP-Protokoll implementiert
Der Host:
- Initiiert Verbindungen zu MCP-Servern
- Sendet Anfragen für Tools, Resources und Prompts
- Verarbeitet Server-Responses
- Managed den Lifecycle der Verbindungen
2. MCP Server
Ein Server stellt Capabilities bereit:
| Capability | Beschreibung | Beispiel |
|---|---|---|
| Tools | Ausführbare Funktionen | search_database, send_email |
| Resources | Lesbare Datenquellen | Dateien, Datenbank-Schemas, APIs |
| Prompts | Vordefinierte Prompt-Templates | Analyse-Workflows, Report-Generierung |
3. Transport Layer
MCP unterstützt verschiedene Transport-Mechanismen:
// stdio - Für lokale Prozesse
{
"command": "python",
"args": ["server.py"],
"transport": "stdio"
}
// SSE - Für Remote-Server
{
"url": "https://api.example.com/mcp",
"transport": "sse"
}Der Verbindungs-Lifecycle
Einen MCP-Server bauen
Jetzt wird es praktisch: Wir bauen einen MCP-Server, der Zugriff auf eine SQLite-Datenbank bietet.
Projektstruktur
mcp-sqlite-server/
├── src/
│ └── index.ts
├── package.json
├── tsconfig.json
└── README.md
Setup
mkdir mcp-sqlite-server && cd mcp-sqlite-server
npm init -y
npm install @modelcontextprotocol/sdk better-sqlite3
npm install -D typescript @types/node @types/better-sqlite3Der Server-Code
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import Database from "better-sqlite3";
// Datenbank initialisieren
const db = new Database("./data.db");
// Server erstellen
const server = new Server(
{
name: "sqlite-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// Tools definieren
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "query",
description: "Führt eine SELECT-Abfrage auf der Datenbank aus",
inputSchema: {
type: "object",
properties: {
sql: {
type: "string",
description: "Die SQL SELECT-Abfrage",
},
},
required: ["sql"],
},
},
{
name: "execute",
description: "Führt eine INSERT/UPDATE/DELETE-Abfrage aus",
inputSchema: {
type: "object",
properties: {
sql: {
type: "string",
description: "Die SQL-Anweisung",
},
params: {
type: "array",
description: "Parameter für Prepared Statements",
items: { type: "string" },
},
},
required: ["sql"],
},
},
],
}));
// Tool-Ausführung
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "query": {
// Nur SELECT erlauben
const sql = args?.sql as string;
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
throw new Error("Nur SELECT-Abfragen erlaubt");
}
const rows = db.prepare(sql).all();
return {
content: [
{
type: "text",
text: JSON.stringify(rows, null, 2),
},
],
};
}
case "execute": {
const sql = args?.sql as string;
const params = (args?.params as string[]) || [];
// Gefährliche Operationen verhindern
const upperSql = sql.trim().toUpperCase();
if (upperSql.startsWith("DROP") || upperSql.startsWith("TRUNCATE")) {
throw new Error("DROP und TRUNCATE sind nicht erlaubt");
}
const result = db.prepare(sql).run(...params);
return {
content: [
{
type: "text",
text: JSON.stringify({
changes: result.changes,
lastInsertRowid: result.lastInsertRowid,
}),
},
],
};
}
default:
throw new Error(`Unbekanntes Tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Fehler: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
// Resources: Datenbank-Schema bereitstellen
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "sqlite://schema",
name: "Datenbank-Schema",
description: "Das Schema aller Tabellen in der Datenbank",
mimeType: "application/json",
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === "sqlite://schema") {
const tables = db
.prepare(
`SELECT name, sql FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'`
)
.all();
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(tables, null, 2),
},
],
};
}
throw new Error(`Unbekannte Resource: ${uri}`);
});
// Server starten
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("SQLite MCP Server gestartet");
}
main().catch(console.error);TypeScript-Konfiguration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}Build und Test
# Kompilieren
npx tsc
# Testen (manuell)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}' | node dist/index.jsIntegration in Claude Desktop
Nach dem Build können Sie den Server in Claude Desktop registrieren:
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%/Claude/claude_desktop_config.json (Windows)
{
"mcpServers": {
"sqlite": {
"command": "node",
"args": ["/pfad/zu/mcp-sqlite-server/dist/index.js"],
"env": {
"NODE_ENV": "production"
}
}
}
}Nach dem Neustart von Claude Desktop steht der Server zur Verfügung.
Best Practices für MCP-Server
1. Sicherheit
// Input-Validierung
function validateSql(sql: string): boolean {
const forbidden = ["DROP", "TRUNCATE", "DELETE FROM", "UPDATE"];
const upperSql = sql.toUpperCase();
return !forbidden.some((keyword) => upperSql.includes(keyword));
}
// Prepared Statements verwenden
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const user = stmt.get(userId);2. Error Handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
// Tool-Logik
return { content: [{ type: "text", text: result }] };
} catch (error) {
// Strukturierte Fehler zurückgeben
return {
content: [
{
type: "text",
text: JSON.stringify({
error: true,
message: error.message,
code: error.code || "UNKNOWN",
}),
},
],
isError: true,
};
}
});3. Logging
import { Logger } from "./logger";
const logger = new Logger("mcp-sqlite");
server.setRequestHandler(CallToolRequestSchema, async (request) => {
logger.info("Tool aufgerufen", {
tool: request.params.name,
args: request.params.arguments,
});
// ... Tool-Logik
logger.debug("Tool abgeschlossen", { duration: Date.now() - start });
});4. Dokumentation
Jedes Tool sollte ausführlich dokumentiert sein:
{
name: "query",
description: `
Führt eine SELECT-Abfrage auf der SQLite-Datenbank aus.
Beispiele:
- "SELECT * FROM users WHERE active = 1"
- "SELECT name, email FROM users LIMIT 10"
Einschränkungen:
- Nur SELECT-Abfragen erlaubt
- Maximal 1000 Ergebnisse
`,
inputSchema: {
type: "object",
properties: {
sql: {
type: "string",
description: "Eine gültige SQL SELECT-Abfrage",
examples: ["SELECT * FROM users", "SELECT COUNT(*) FROM orders"],
},
},
required: ["sql"],
},
}Fortgeschrittene Patterns
Streaming für große Datenmengen
// Für große Result-Sets: Pagination implementieren
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { offset = 0, limit = 100 } = request.params.arguments || {};
const rows = db
.prepare(`SELECT * FROM large_table LIMIT ? OFFSET ?`)
.all(limit, offset);
return {
content: [
{
type: "text",
text: JSON.stringify({
data: rows,
pagination: {
offset,
limit,
hasMore: rows.length === limit,
},
}),
},
],
};
});Prompt-Templates
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [
{
name: "analyze-table",
description: "Analysiert die Struktur und Daten einer Tabelle",
arguments: [
{
name: "tableName",
description: "Name der zu analysierenden Tabelle",
required: true,
},
],
},
],
}));
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "analyze-table") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Analysiere die Tabelle "${args?.tableName}":
1. Zeige das Schema der Tabelle
2. Wie viele Einträge gibt es?
3. Welche Spalten haben NULL-Werte?
4. Gibt es Duplikate?
Nutze die verfügbaren Tools um diese Fragen zu beantworten.`,
},
},
],
};
}
throw new Error(`Unbekannter Prompt: ${name}`);
});Debugging und Entwicklung
MCP Inspector
Anthropic bietet einen Inspector für die Entwicklung:
npx @modelcontextprotocol/inspector node dist/index.jsDieser öffnet eine Web-UI zum Testen Ihrer Tools und Resources.
Logging während der Entwicklung
// Alle JSON-RPC Nachrichten loggen
process.stdin.on("data", (data) => {
console.error("← Eingehend:", data.toString());
});
const originalWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = (chunk: any, ...args: any[]) => {
console.error("→ Ausgehend:", chunk.toString());
return originalWrite(chunk, ...args);
};Ausblick
MCP ist noch jung, aber die Entwicklung schreitet schnell voran:
- Mehr Transports: WebSocket-Support, gRPC
- Authentifizierung: OAuth, API-Keys auf Protokollebene
- Ecosystem: Wachsende Bibliothek von Community-Servern
- IDE-Integration: VS Code Extensions, JetBrains Plugins
Das Protokoll hat das Potenzial, zum Standard für KI-Tool-Integration zu werden – ähnlich wie HTTP für Web-Kommunikation.
Fazit
MCP bietet eine elegante Lösung für das Problem der KI-Tool-Integration:
- Standardisierung: Ein Protokoll für alle Tools
- Sicherheit: Klare Grenzen zwischen Client und Server
- Wiederverwendbarkeit: Ein Server, viele Clients
- Erweiterbarkeit: Einfach neue Capabilities hinzufügen
Mit dem Wissen aus diesem Artikel können Sie:
- Die MCP-Architektur verstehen und erklären
- Eigene MCP-Server für Ihre Anwendungsfälle bauen
- Bestehende Server evaluieren und integrieren
Interesse an einer MCP-Integration für Ihr Unternehmen? Kontaktieren Sie mich für eine technische Beratung.