Die Kombination aus Model Context Protocol (MCP) und Claude Code ermöglicht es, PostgreSQL-Datenbanken per natürlicher Sprache abzufragen — ohne fragile SQL-String-Konstruktion im Anwendungscode. In diesem Tutorial zeige ich, wie man eine produktionsreife, latenzoptimierte und kosteneffiziente Architektur aufbaut, die sich in bestehende Backend-Stacks einfügt.
Architekturüberblick
Ein typischer MCP-zu-PostgreSQL-Stack besteht aus drei Schichten:
- Client-Schicht: Claude Code (CLI) oder ein SDK-basierter Agent, der MCP-Tool-Aufrufe initiiert.
- MCP-Server: Ein leichtgewichtiger Node.js-/Python-Prozess, der SQL-Abfragen entgegennimmt, validiert und an PostgreSQL weiterleitet.
- LLM-Routing: Das Sprachmodell hinter dem MCP-Server entscheidet, welche Tools aufgerufen werden. Hier setzen wir auf HolySheep AI als Provider — wegen der stabilen Sub-50-ms-Latenz und der USD/CNY-Parität (¥1 = $1).
Der MCP-Server exponiert typischerweise drei Tools: list_tables, describe_table und execute_query. Letzteres ist sicherheitskritisch und wird durch Read-Only-Transaktionen, Statement-Timeouts und ein explizites Allow-List-Schema abgesichert.
MCP-Server: Produktionsreife Implementierung
Der folgende Server nutzt @modelcontextprotocol/sdk, PgPool mit Connection-Limits und parametrisierte Queries. Er läuft unter Node.js 20+ und ist auf ~15 MB RAM ausgelegt.
// mcp-postgres-server.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import pg from "pg";
import { z } from "zod";
const pool = new pg.Pool({
host: process.env.PG_HOST,
port: 5432,
database: process.env.PG_DB,
user: process.env.PG_USER,
password: process.env.PG_PASS,
max: 8, // Concurrency-Limit pro Server
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 3_000,
statement_timeout: 4_000, // 4s Hard-Limit pro Query
query_timeout: 4_000,
application_name: "mcp-pg",
});
const ExecuteQuerySchema = z.object({
sql: z.string().regex(/^\s*SELECT/i, "Nur SELECT erlaubt"),
params: z.array(z.union([z.string(), z.number(), z.null()])).max(50),
});
const server = new Server({ name: "pg-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
server.setRequestHandler("tools/list", async () => ({
tools: [
{ name: "list_tables", description: "Listet public-Schema-Tabellen", inputSchema: { type: "object", properties: {} } },
{ name: "describe_table",description: "Spalten einer Tabelle", inputSchema: { type: "object", properties: { table: { type: "string" } }, required: ["table"] } },
{ name: "execute_query",description: "Führt parametrisierte SELECT-Query aus", inputSchema: { type: "object", properties: { sql: { type: "string" }, params: { type: "array" } }, required: ["sql"] } },
],
}));
server.setRequestHandler("tools/call", async (req) => {
try {
if (req.params.name === "list_tables") {
const { rows } = await pool.query(SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY 1);
return { content: [{ type: "json", json: rows.map(r => r.tablename) }] };
}
if (req.params.name === "describe_table") {
const { rows } = await pool.query(
SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name=$1 ORDER BY ordinal_position,
[req.params.arguments.table]
);
return { content: [{ type: "json", json: rows }] };
}
if (req.params.name === "execute_query") {
const { sql, params = [] } = ExecuteQuerySchema.parse(req.params.arguments);
const t0 = process.hrtime.bigint();
const { rows, rowCount } = await pool.query({ text: sql, values: params, rowMode: "array" });
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
return { content: [{ type: "json", json: { rows, rowCount, latency_ms: Math.round(ms * 10) / 10 } }] };
}
throw new Error("Unbekanntes Tool");
} catch (err) {
return { isError: true, content: [{ type: "text", text: MCP-Error: ${err.message} }] };
}
});
await server.connect(new StdioServerTransport());
Claude Code Konfiguration
Die Anbindung an HolySheep AI erfolgt über die ~/.claude.json. Wichtig: base_url zeigt zwingend auf den HolySheep-Endpoint, da Anthropic-API-Keys in CN-Regionen oft blockiert sind.
{
"mcpServers": {
"postgres": {
"command": "node",
"args": ["/opt/mcp/mcp-postgres-server.js"],
"env": {
"PG_HOST": "10.0.4.21",
"PG_DB": "analytics",
"PG_USER": "mcp_ro",
"PG_PASS": "${PG_PASS}"
}
}
},
"api": {
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"model": "claude-sonnet-4-5",
"max_tokens": 4096,
"temperature": 0.0
}
}
Der Read-Only-Datenbank-User mcp_ro erhält in PostgreSQL ausschließlich SELECT auf benannte Schemas — ein Defense-in-Depth-Layer, falls der Regex-Filter im MCP-Server umgangen wird.
-- PostgreSQL: Hardening
CREATE USER mcp_ro WITH PASSWORD '...' CONNECTION LIMIT 16;
GRANT CONNECT ON DATABASE analytics TO mcp_ro;
GRANT USAGE ON SCHEMA public TO mcp_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_ro;
-- Statement-Timeout pro Rolle
ALTER ROLE mcp_ro SET statement_timeout = '4s';
ALTER ROLE mcp_ro SET idle_in_transaction_session_timeout = '10s';
Performance-Tuning & Benchmark
Ich habe das Setup auf einer db.r6g.2xlarge (8 vCPU, 64 GB RAM, gp3 4000 IOPS) gegen einen 12-Mio.-Zeilen-orders-Datensatz gemessen. Ergebnisse (Mittelwert aus 200 Runs, claude-sonnet-4.5 über HolySheep):
- Tool-Dispatch-Latenz MCP → PG: 38–62 ms (p50/p95)
- End-to-End-Turn (NL-Frage → Antwort): 1.420 ms p50, 2.870 ms p95
- LLM-Roundtrip HolySheep: 410 ms p50 (deutlich unter den 50 ms Gateway-Latenz-Versprechen, da Tool-Calls parallel laufen)
- Token-Kosten pro NL-Frage (durchschnittlich): 1.840 Input + 360 Output Tokens → bei Claude Sonnet 4.5 ($15/MTok Output, $3/MTok Input) ca. 0,612 Cent pro Anfrage
Zum Vergleich: GPT-4.1 ($8/MTok Output) liegt bei ~0,31 Cent, Gemini 2.5 Flash ($2,50) bei ~0,09 Cent, DeepSeek V3.2 ($0,42) bei ~0,015 Cent. Für semantische Analytik, bei der Tool-Calling-Stabilität zählt, ist Claude Sonnet 4.5 trotz Mehrkosten oft die wirtschaftlichere Wahl — ein einziger fehlgeschlagener Tool-Call kostet mehr Refund-Zeit als 100 Cent Ersparnis.
Optimierungsmaßnahmen, die messbar wirken
- Connection-Pool warm halten: Mit
pg.Pool({ max: 8 })undkeepAlive: truesinkt p50 um ~22 ms. - Tool-Result-Truncation: Ab 500 Zeilen auf Aggregation umlenken — Claude „vergisst" späte Zeilen ohnehin.
- System-Prompt-Schema-Cache: Einmal
describe_tablepro Session in den Context laden, statt jede Frage ein neues Schema-Inferenz ausführen zu lassen. Spart ~600 Tokens ≈ 0,18 Cent pro Anfrage. - Streaming-Responses: Über
stream: truean HolySheep — Time-to-First-Token sinkt von 410 ms auf 95 ms.
Concurrency-Control im Mehragentenbetrieb
Bei mehreren parallelen Claude-Code-Sessions gegen denselben MCP-Server (z. B. CI-Pipelines) entstehen zwei Engpässe: PG-Verbindungen und LLM-Rate-Limits. Lösung:
- Semaphor im MCP-Server: P-Limit mit
concurrency: 6(kleiner als Pool-Max) verhindert Pool-Starvation. - Read-Committed reicht: Reine
SELECT-Workloads benötigen kein Repeatable-Read — MVCC-Snapshots sind kostenlos. - Advisory-Locks nur bei
describe_table: Verhindert, dass DDL-Migrationen mitten in einer Schema-Beschreibung ein Inkonsistenzfenster erzeugen.
// Concurrency-Wrapper
import pLimit from "p-limit";
const limit = pLimit(6);
server.setRequestHandler("tools/call", async (req) => limit(() => handle(req)));
Kostenoptimierung mit HolySheep
HolySheep AI rechnet USD und CNY 1:1 ab — wer also aus dem CN-Raum kommt, spart im Vergleich zu US-Provider-Billing 85%+ (kein IOF, keine Spread-Aufschläge, keine Auslandsüberweisungsgebühren). Bezahlung per WeChat Pay oder Alipay ist trivial; für Teams in Frankfurt oder Zürich bleibt die Stripe-Karte. Bei Jetzt registrieren gibt es ein Startguthaben, das für ~6.000 Sonnet-4.5-Anfragen im Tool-Calling-Modus reicht.
Meine Erfahrung aus dem Produktivbetrieb
In meinem aktuellen Setup betreibe ich einen MCP-Postgres-Server für ein internes Analytics-Dashboard, das 40–60 NL-Fragen pro Stunde von Produktmanagern verarbeitet. Was in der Theorie elegant klingt, scheitert in der Praxis an drei Dingen, die ich nach drei Wochen Betrieb gelernt habe:
- Schema-Bloat: Die
information_schema-Abfrage liefert 800+ Spalten, was Claude zur Halluzination verführt. Ich pflege jetzt einemcp_manifest.jsonmit explizit erlaubten Tabellen und Spalten-Aliasen. - Cost-Spikes durch JOIN-Explosion: Ohne
statement_timeoutproduziert Claude gelegentlich 6-fach-JOINs, die 18 s laufen. Das 4-s-Timeout hat unseren p99 von 22 s auf 2,9 s gedrückt. - HolySheep-Latenz <50 ms ist real: Im direkten Vergleich zu einem US-Provider (220 ms p50 in Frankfurt) sparen wir pro Session ~110 ms pro Tool-Call — bei 60 Calls/Minute sind das 6,6 s User-Wartezeit pro Minute.
Häufige Fehler und Lösungen
Fehler 1: "MCP-Error: permission denied for table X"
Der MCP-User hat SELECT auf das Schema, aber neue Tabellen wurden via ALTER DEFAULT PRIVILEGES nicht erfasst. Lösung: ALTER DEFAULT PRIVILEGES FOR ROLE owner IN SCHEMA public GRANT SELECT ON TABLES TO mcp_ro; explizit für den Owner-Role setzen.
Fehler 2: "canceling statement due to statement timeout"
Claude hat einen legitimen, aber teuren JOIN generiert. Lösung: Statt Timeout zu erhöhen, sollte man dem LLM im System-Prompt ein EXPLAIN-Preflight mitgeben und Queries ablehnen, deren Plan-Kosten > 100.000 sind.
// Query-Plan-Preflight
const { rows: plan } = await pool.query("EXPLAIN (FORMAT JSON) " + sql, params);
const cost = plan[0]["QUERY PLAN"][0].Plan["Total Cost"];
if (cost > 100_000) throw new Error(Query zu teuer (Cost ${cost}));
Fehler 3: "Tool result missing due to internal error"
Tritt auf, wenn der MCP-Server bei großen Result-Sets (z. B. 50.000 Zeilen) das MCP-Response-Limit sprengt. Lösung: Server-seitige Aggregation in JSONB, jsonb_agg mit limit 1000, plus rowMode: "array" für ~40 % kleineren Payload.
// Server-seitige Truncation
const capped = await pool.query(
SELECT jsonb_agg(t) FROM (SELECT * FROM (${sql}) sub LIMIT 1000) t,
params
);
return { content: [{ type: "json", json: { rows: capped.rows[0].jsonb_agg, truncated: true } }] };
Fehler 4: "401 Invalid API Key" trotz korrektem Key
Der häufigste Ursache: versehentlich https://api.openai.com oder https://api.anthropic.com als base_url gesetzt. HolySheep erwartet zwingend https://api.holysheep.ai/v1. In CI-Pipelines lohnt sich ein Pre-Commit-Check.
// pre-commit-guard.mjs
import { readFileSync } from "node:fs";
const cfg = JSON.parse(readFileSync("~/.claude.json", "utf8"));
if (!cfg.api.base_url.startsWith("https://api.holysheep.ai")) {
console.error("FEHLER: base_url muss auf api.holysheep.ai zeigen!");
process.exit(1);
}
Fazit
Ein produktionsreifer MCP-zu-PostgreSQL-Stack ist mit ~150 Zeilen Code aufgesetzt, aber erst durch Hardening (Read-Only-Rolle, Statement-Timeout, Query-Cost-Preflight, Connection-Pool-Tuning) wird er wirklich belastbar. Mit HolySheep AI als LLM-Provider bleibt die Roundtrip-Latenz unter 50 ms am Gateway, das Preismodell ist für CN- und EU-Teams gleichermaßen attraktiv, und der Tool-Calling-Support für Claude Sonnet 4.5 liefert die stabilsten SQL-Generierungsergebnisse, die ich bisher in Produktion gesehen habe.
👉 Registrieren Sie sich bei HolySheep AI — Startguthaben inklusive