bifroest/services/db-mcp/README.md
2026-10-27 08:34:17 +01:00

9.8 KiB
Raw Blame History

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 mit default_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 im WITH (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 mit CREATE SESSION und SELECT-Rechten hier besonders wichtig. check_connection meldet 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 /live und /ready ohne.
  • 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änkter securityContext), 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_tables zeigt 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 aus DBMCP_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_HOSTS und 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 rufen check_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.
  • structuredContent mit columns und rows wird 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__query nur mit Freigabe) und Audit-Log bleiben in Bifröst.
  • Lokal startet scripts/dev.sh den Dienst für PostgreSQL auf Port 8791, mit --oracle den für Oracle auf Port 8792, und zeigt URL und Token.