9.8 KiB
db-mcp – lesende MCP-Server für Datenbanken
Ein gemeinsamer Kern (dbmcp.core), darauf je Datenbanktyp ein eigener Dienst mit eigenem Image: db-mcp-postgres (dbmcp.postgres) und db-mcp-oracle (dbmcp.oracle), weitere wie MS SQL mit derselben Schnittstelle. Der Name ist bewusst neutral; jeder MCP-Client kann die Dienste nutzen, Bifröst ist einer davon.
Stand: 25.10.2026. In Bifröst sind Datenbanken MCP-Verbindungen eines Workspace über diese Dienste (siehe Einbindung in Bifröst).
Aufteilung
| Bifröst (bzw. ein anderer Client) | DB-Dienst |
|---|---|
| Verbindungen je Workspace, Zugangsdaten (verschlüsselt), Bestätigung der Sichtbarkeit, Rechte, Regeln, Freigaben | Datenbanktreiber, SQL-Dialekt in der Anleitung (instructions) |
| Audit-Log der Nutzer, Tabellenansicht, CSV, Diagramm | Nur lesen durchsetzen, Grenzen, Audit-Log der Abfragen |
| Schickt Zugangsdaten je Aufruf als Header | Speichert keine Zugangsdaten |
Andere MCP-Clients (OpenCode, Claude Desktop, eigene Agenten) können den Dienst direkt nutzen. Sie tragen Schlüssel und Zugangsdaten als Header in ihrer Konfiguration ein und sind selbst für die Kontrolle zuständig.
Tools
Alle Dienste bieten dieselben Tools mit denselben Parametern, alle gekennzeichnet als nur lesend (readOnlyHint):
| Tool | Parameter | Ergebnis |
|---|---|---|
check_connection |
– | Version, sichtbare Tabellen, Zahl der Objekte mit Schreibrecht (empfohlen: 0) |
list_tables |
– | schema, name, type (table/view), rows_estimate, comment |
describe_table |
table (gern schema.tabelle) |
Spalten (name, type, nullable, comment) und unter sample die ersten Zeilen |
query |
sql |
Genau eine lesende Abfrage, Zeilen begrenzt |
Jedes Ergebnis kommt als Text für das Modell (Markdown-Tabelle) und strukturiert (structuredContent):
{"columns": ["id", "name"], "rows": [[1, "Kunde 1"]], "row_count": 1, "truncated": false}
Werte sind JSON: Zahlen (auch numeric, außer bei mehr als 15 Stellen als Text), Datum/Zeit als ISO-Text, Binärdaten als base64:… (gekürzt).
Zugangsdaten je Aufruf (Header)
| Header | Pflicht | Bedeutung |
|---|---|---|
Authorization: Bearer <schlüssel> |
ja | Schlüssel des Clients (DBMCP_API_KEYS) |
X-DB-Host |
ja | Muss in DBMCP_ALLOWED_HOSTS stehen |
X-DB-Port |
nein | Standard des Treibers (PostgreSQL 5432, Oracle 1521) |
X-DB-Database, X-DB-User |
ja | |
X-DB-Password |
nein | |
X-DB-SSLMode |
nein | verify-full (Standard), require; prefer/disable nur für lokale Hosts. Bei Oracle: require/verify-full = TCPS, verify-full prüft zusätzlich den Hostnamen im Zertifikat |
Bei Oracle steht in X-DB-Database der Service-Name (z. B. ORCLPDB1).
Die Zugangsdaten gehören nie in Tool-Argumente: Sonst stünden sie im Prompt und im Verlauf eines Chats.
Sicherheit
- Nur lesen, mehrfach: DB-Nutzer mit reinen Leserechten (prüft
check_connection), Verbindung mitdefault_transaction_read_only, schreibgeschützte Transaktion mit Rollback, genau eine Anweisung (serverseitiger Cursor), Prüfung des ersten Schlüsselworts. - Oracle: schreibgeschützte Transaktion (
SET TRANSACTION READ ONLY) mit Rollback; oracledb führt je Aufruf nur eine Anweisung aus; PL/SQL-Blöcke schließt das erste Schlüsselwort aus, PL/SQL imWITH(WITH FUNCTION) wird abgewiesen, weil eine Funktion mit autonomer Transaktion sonst schreiben könnte. Vorhandene Funktionen mit EXECUTE-Recht kann eine Abfrage aufrufen; darum ist ein Nutzer nur mitCREATE SESSIONund SELECT-Rechten hier besonders wichtig.check_connectionmeldet auch systemweite Rechte (INSERT ANY TABLE…). - Grenzen: Zeitlimit in der Datenbank und zusätzlich vom Dienst, Zeilenlimit, Länge der Abfrage.
- Nur freigegebene Hosts (
DBMCP_ALLOWED_HOSTS, exakt oder als Endung.db.intern): Der Dienst ist kein Sprungbrett ins Netz (SSRF). - Anmeldung: jeder Aufruf mit Schlüssel; nur
/liveund/readyohne. - Audit-Log: eine JSON-Zeile je Aufruf auf stdout mit Client, Datenbank (ohne Passwort), Tool, Abfrage, Zeilen, Dauer, Ergebnis.
- Netz auf OpenShift: Ingress nur von Bifröst, Egress nur zu den Datenbank-Servern (siehe
deploy/).
Einstellungen
| Variable | Standard | Bedeutung |
|---|---|---|
DBMCP_API_KEYS |
– (Pflicht) | name:geheimnis,…; Geheimnis mindestens 16 Zeichen; der Name erscheint im Audit-Log |
DBMCP_ALLOWED_HOSTS |
leer | Erlaubte Datenbank-Hosts, kommagetrennt |
DBMCP_ALLOW_LOCAL |
false |
localhost erlauben (nur Entwicklung) |
DBMCP_QUERY_TIMEOUT_SECONDS |
15 | Zeitlimit je Abfrage |
DBMCP_CONNECT_TIMEOUT_SECONDS |
5 | Zeitlimit für den Verbindungsaufbau |
DBMCP_MAX_ROWS |
500 | Höchstzahl Zeilen je Ergebnis |
DBMCP_HTTP_HOSTS |
leer | Erlaubte Host-Header (Schutz vor DNS-Rebinding); leer = keine Prüfung |
DBMCP_BIND, DBMCP_PORT |
0.0.0.0, 8080 |
Adresse des Dienstes |
DBMCP_INSECURE_NO_AUTH |
false |
Nur Entwicklung: ohne Schlüssel starten |
Lokal starten und testen
Der Dienst läuft in der Entwicklung mit der Umgebung von Bifröst (alle Pakete sind dort schon installiert):
cd services/db-mcp
DBMCP_API_KEYS="dev:dev-schluessel-12345678" DBMCP_ALLOW_LOCAL=true DBMCP_PORT=8791 \
../../.venv/bin/python -m dbmcp.postgres
../../.venv/bin/python -m pytest -q -p no:cacheprovider # braucht das Test-Postgres auf Port 55433
Oracle zum Testen (Oracle Database Free im Container, ca. 6 GB; die Tests nutzen nur SQL, das es in 19c schon gibt, und werden übersprungen, wenn der Container nicht läuft):
cd ../.. && docker compose --profile oracle up -d oracle # 127.0.0.1:55434, Service FREEPDB1, system/aiw
cd services/db-mcp
DBMCP_API_KEYS="dev:dev-schluessel-12345678" DBMCP_ALLOW_LOCAL=true DBMCP_PORT=8792 \
../../.venv/bin/python -m dbmcp.oracle
URL=http://127.0.0.1:8792/mcp DBPORT=55434 DBNAME=FREEPDB1 DBUSER=system scripts/dbcall.sh check
Bequemer mit scripts/dbcall.sh (fragt standardmäßig die lokale Bifröst-Datenbank ab, Zugangsdaten über Umgebungsvariablen überschreibbar):
scripts/dbcall.sh check
scripts/dbcall.sh tables
scripts/dbcall.sh describe public.workspaces
scripts/dbcall.sh query "SELECT count(*) FROM users"
DBPASS=falsch scripts/dbcall.sh check # Fehlerfall
Aufruf ohne MCP-Client, z. B. mit curl (der Dienst ist zustandslos, eine Sitzung ist nicht nötig):
curl -s -X POST http://127.0.0.1:8791/mcp \
-H "Authorization: Bearer dev-schluessel-12345678" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: 2025-06-18" \
-H "X-DB-Host: 127.0.0.1" -H "X-DB-Port: 55433" -H "X-DB-Database: aiw" -H "X-DB-User: aiw" \
-H "X-DB-Password: aiw" -H "X-DB-SSLMode: disable" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"query","arguments":{"sql":"SELECT 1"}}}'
Image und OpenShift
Containerfile.postgres,Containerfile.oracle: UBI-Image mit Python 3.12 und nur dem jeweiligen Treiber, läuft ohne root mit zufälliger UID. Oracle braucht keinen Oracle-Client (Thin-Modus).deploy/openshift-postgres.yaml,deploy/openshift-oracle.yaml: Beispiel mit Secret, Deployment (zwei Replikas, Probes, Limits, eingeschränktersecurityContext), Service und NetworkPolicies für Ingress und Egress. Braucht keine Cluster-Admin-Rechte.
Der Dienst ist zustandslos (stateless_http): Mehrere Pods brauchen keine gemeinsame Sitzung.
Weitere Datenbanktypen
Neues Paket dbmcp/<typ>/ mit einem Treiber nach dbmcp.core.driver (session() mit check, tables, describe, query, dazu name, key, default_port, starts), einem __main__.py, einer Extra-Abhängigkeit in pyproject.toml und einem eigenen Containerfile.<typ>. Kern, Tools, Header, Ergebnisformat und Audit-Log bleiben gleich. Tests nach dem Muster von tests/test_oracle.py mit einer Test-Datenbank im Container.
Offene Punkte Oracle
- Native Network Encryption: Verlangt der Server die Oracle-eigene Verschlüsselung (
SQLNET.ENCRYPTION_SERVER = required) statt TLS, reicht der Thin-Modus nicht (Fehler DPY-3001). Dann TCPS am Server oder ein Image mit Oracle Instant Client (Thick-Modus). - Synonyme:
list_tableszeigt Tabellen und Views, keine Synonyme. Abfragen über Synonyme funktionieren.
Einbindung in Bifröst
- In Bifröst heißt ein Dienst Datenbank-Treiber. Ein Plattform-Admin registriert ihn einmal unter Administration → Datenbank-Treiber: URL
http://db-mcp-oracle:8080/mcp, Schlüssel des Dienstes ausDBMCP_API_KEYS(öffnet nur den Dienst, keine Datenbank). Dann Tools abrufen und die vier Tools einstufen (Vorschlag: lesend, erlaubt). - Workspace-Admins legen unter Einstellungen → MCP-Server → Datenbank hinzufügen Verbindungen an: Datenbanktyp (einer der Treiber), Kurzname, Host, Port, Datenbank, Nutzer, Passwort, TLS. Bifröst prüft den Host zusätzlich gegen
AIW_DB_ALLOWED_HOSTSund speichert das Passwort verschlüsselt. - Die Tools der Verbindung heißen
<kurzname>__list_tables,…__describe_table,…__query; sie haben die Einstufung des Treibers (gleicher Fingerabdruck) und werden nicht einzeln eingestuft. Speichern und Testen rufencheck_connection. - Bei jedem Aufruf hängt Bifröst Schlüssel und Zugangsdaten als Header an (
Authorization,X-DB-*); das Modell sieht sie nicht. Den SQL-Dialekt liest das Modell aus der Anleitung des Dienstes. structuredContentmitcolumnsundrowswird zur Tabellenansicht (Tabelle, CSV, Diagramm); das gilt für jedes MCP-Tool, nicht nur für Datenbanken.- Bestätigung der Sichtbarkeit, Vorgaben je Verbindung (z. B.
prod__querynur mit Freigabe) und Audit-Log bleiben in Bifröst. - Lokal startet
scripts/dev.shden Dienst für PostgreSQL auf Port 8791, mit--oracleden für Oracle auf Port 8792, und zeigt URL und Token.