MCP-DB Server

Erstellt: 2025-12-20 | Aktualisiert: 2025-12-31

Model Context Protocol Server für sichere Datenbankzugriffe durch Claude Code.

Version2.0.0
Transportstdio
RuntimePython 3.13+
Pfad/var/www/mcp-servers/mcp_db/
Tools10

Zweck

Kapitel

KapitelBeschreibung
Cheat-SheetSchnellreferenz: Alt (mysql) vs Neu (MCP-DB)
ArchitekturVerzeichnisstruktur, Komponenten, Prinzipien
ContractsQueryRequest, QueryResponse, ExecuteContract
KonfigurationEnvironment Variables, Allowlists, Limits
ValidatorsQueryValidator, ExecuteValidator
InfrastructureDatabaseConnection
Tools10 Tools: SELECT, DDL, CRUD, Schema-Inspektion
SicherheitMaßnahmen, Mandatory WHERE, LIMIT-Schutz
InstallationSetup, Registrierung, Test
TestprotokollTestfälle, Testmatrix, Prüfbericht

Schnellstart

# Registrierung in Claude Code
claude mcp add mcp-db \
  --transport stdio \
  -- /var/www/mcp-servers/mcp_db/venv/bin/python \
     /var/www/mcp-servers/mcp_db/server.py

# Status prüfen
claude mcp list

Verfügbare Tools (10)

Lese-Operationen

ToolBeschreibungParameter
db_select SELECT-Abfrage ausführen query, database, max_rows, params
db_schema Tabellen mit Metadaten anzeigen database
db_stats Logging-Statistiken aus mcp_log limit
db_databases Erlaubte Datenbanken auflisten -
db_tables Tabellen einer DB auflisten database, include_row_count
db_describe Tabellenstruktur anzeigen table, database, show_create

Schreib-Operationen

ToolBeschreibungParameter
db_insert Datensatz einfügen table, data, database
db_update Datensätze aktualisieren table, data, where, database
db_delete Datensätze löschen table, where, database, limit

DDL-Operationen

ToolBeschreibungParameter
db_execute DDL ausführen (ALTER, CREATE, DROP, TRUNCATE) statement, database, params

Datenbank-Struktur

DatenbankZweckBeispiel-Tabellen
ki_devDevelopment/Infrastructuretasks, contracts, dokumentation, mcp_log
ki_contentContent/User-facingchat_sessions, chat_messages, content, personas

Sicherheitsübersicht

MaßnahmeStatusBetrifft
Prepared StatementsImplementiertAlle Tools
Identifier-ValidierungRegex ^[a-zA-Z0-9_]+$INSERT, UPDATE, DELETE
Mandatory WHEREErzwungenUPDATE, DELETE
Default LIMIT 100AktivDELETE
DDL Statement-ValidierungExecuteValidatordb_execute
Database Allowlistki_dev, ki_contentAlle Tools
Keyword Blocklist (15)Aktivdb_select
Query Timeout30 Sekundendb_select
Row LimitMax 100db_select
LoggingJede Operation in mcp_logAlle Tools
Blocking-HookPreToolUse für Bashmysql/mariadb CLI

Changelog

VersionDatumÄnderungen
2.0.02025-12-20+7 neue Tools: db_execute, db_describe, db_databases, db_tables, db_insert, db_update, db_delete
1.0.02025-12-20Initial Release: db_select, db_schema, db_stats

Verwandte Themen

MCP-DB Tools

Erstellt: 2025-12-20 | Aktualisiert: 2025-12-29

10 MCP Tools für vollständige Datenbankoperationen.

Übersicht

KategorieToolBeschreibung
Lesendb_selectSELECT-Abfragen ausführen
db_schemaTabellen-Metadaten anzeigen
db_statsMCP-Log Statistiken
db_databasesErlaubte Datenbanken auflisten
db_tablesTabellen einer DB auflisten
db_describeTabellenstruktur anzeigen
Schreibendb_insertDatensatz einfügen
db_updateDatensätze aktualisieren
db_deleteDatensätze löschen
DDLdb_executeDDL-Statements ausführen

Datenbank-Zuordnung

DatenbankInhalt
ki_devInfrastruktur: Tasks, Contracts, Docs, Pipeline, Logs, Protokoll
ki_contentContent: Chat, Wissen, Entitäten, Taxonomie, Documents, Chunks

db_select

Führt SELECT-Abfragen aus.

Signatur

def db_select(
    query: str,
    database: str = "ki_dev",
    max_rows: int = 100,
    params: list | None = None
) -> dict

Parameter

ParameterTypDefaultBeschreibung
querystr-SQL SELECT Statement
databasestr"ki_dev"Zieldatenbank
max_rowsint100Maximale Ergebniszeilen (1-100)
paramslistNonePrepared Statement Parameter

Response

{
    "status": "success",
    "data": [{"id": 1, "name": "Test"}, ...],
    "row_count": 10,
    "execution_ms": 45
}

Beispiele

# Einfache Abfrage
db_select("SELECT * FROM mcp_log ORDER BY timestamp DESC LIMIT 10", database="ki_dev")

# Mit Prepared Statements
db_select(
    query="SELECT * FROM tasks WHERE status = %s",
    database="ki_dev",
    params=["pending"]
)

db_schema

Zeigt Tabellen einer Datenbank mit Metadaten.

Signatur

def db_schema(database: str = "ki_dev") -> dict

Response

{
    "tables": [
        {"TABLE_NAME": "mcp_log", "TABLE_ROWS": 1234, "CREATE_TIME": "2025-01-15"},
        ...
    ],
    "database": "ki_dev"
}

db_stats

Zeigt Logging-Statistiken aus mcp_log.

Signatur

def db_stats(limit: int = 50) -> dict

Response

{
    "logs": [
        {"id": 123, "timestamp": "2025-01-15 14:30:00", "status": "success", ...},
        ...
    ],
    "count": 50
}

db_databases

Listet alle erlaubten Datenbanken auf.

Signatur

def db_databases() -> dict

Response

{
    "status": "success",
    "databases": [
        {"name": "ki_dev", "table_count": 19},
        {"name": "ki_content", "table_count": 23}
    ]
}

Beispiel

# Erlaubte Datenbanken anzeigen
db_databases()

db_tables

Listet alle Tabellen einer Datenbank auf.

Signatur

def db_tables(
    database: str = "ki_dev",
    include_row_count: bool = False
) -> dict

Parameter

ParameterTypDefaultBeschreibung
databasestr"ki_dev"Zieldatenbank
include_row_countboolFalseRow-Count pro Tabelle

Response

{
    "status": "success",
    "database": "ki_dev",
    "tables": ["tasks", "contracts", "mcp_log", ...],
    "count": 19
}

Beispiel

# Tabellen mit Row-Count
db_tables(database="ki_content", include_row_count=True)

db_describe

Zeigt Tabellenstruktur an (DESCRIBE oder SHOW CREATE TABLE).

Signatur

def db_describe(
    table: str,
    database: str = "ki_dev",
    show_create: bool = False
) -> dict

Parameter

ParameterTypDefaultBeschreibung
tablestr-Tabellenname
databasestr"ki_dev"Zieldatenbank
show_createboolFalseTrue = SHOW CREATE TABLE

Response (DESCRIBE)

{
    "status": "success",
    "table": "chat_sessions",
    "database": "ki_content",
    "columns": [
        {"Field": "id", "Type": "int(11)", "Null": "NO", "Key": "PRI", "Default": null},
        {"Field": "title", "Type": "varchar(255)", "Null": "YES", ...},
        ...
    ]
}

Response (SHOW CREATE)

{
    "status": "success",
    "table": "chat_sessions",
    "create_statement": "CREATE TABLE `chat_sessions` (...)"
}

Beispiele

# Spalten anzeigen
db_describe(table="tasks", database="ki_dev")

# CREATE Statement anzeigen
db_describe(table="tasks", database="ki_dev", show_create=True)

db_insert

Fügt einen Datensatz ein.

Signatur

def db_insert(
    table: str,
    data: dict,
    database: str = "ki_dev"
) -> dict

Parameter

ParameterTypDefaultBeschreibung
tablestr-Zieltabelle
datadict-Spalte:Wert Paare
databasestr"ki_dev"Zieldatenbank

Response

{
    "status": "success",
    "table": "tasks",
    "inserted_id": 42,
    "execution_ms": 12
}

Beispiel

db_insert(
    table="tasks",
    data={
        "title": "Neue Aufgabe",
        "description": "Beschreibung hier",
        "status": "pending",
        "type": "ai_task"
    },
    database="ki_dev"
)

Sicherheit


db_update

Aktualisiert Datensätze. WHERE ist Pflicht!

Signatur

def db_update(
    table: str,
    data: dict,
    where: dict,
    database: str = "ki_dev"
) -> dict

Parameter

ParameterTypDefaultBeschreibung
tablestr-Zieltabelle
datadict-SET-Klausel (Spalte:Wert)
wheredict-WHERE-Klausel (Spalte:Wert) - PFLICHT!
databasestr"ki_dev"Zieldatenbank

Response

{
    "status": "success",
    "table": "tasks",
    "affected_rows": 1,
    "execution_ms": 8
}

Beispiel

db_update(
    table="tasks",
    data={"status": "completed"},
    where={"id": 42},
    database="ki_dev"
)

Sicherheit

UPDATE ohne WHERE ist verboten! Der Aufruf wird mit "denied" abgelehnt.

db_delete

Löscht Datensätze. WHERE ist Pflicht! Default LIMIT: 100

Signatur

def db_delete(
    table: str,
    where: dict,
    database: str = "ki_dev",
    limit: int | None = None
) -> dict

Parameter

ParameterTypDefaultBeschreibung
tablestr-Zieltabelle
wheredict-WHERE-Klausel - PFLICHT!
databasestr"ki_dev"Zieldatenbank
limitint100Max. zu löschende Zeilen

Response

{
    "status": "success",
    "table": "mcp_log",
    "deleted_rows": 5,
    "limit_applied": 100,
    "execution_ms": 15
}

Beispiel

# Alte Logs löschen
db_delete(
    table="mcp_log",
    where={"status": "error"},
    database="ki_dev",
    limit=50
)

Sicherheit

  • DELETE ohne WHERE ist verboten!
  • Default LIMIT 100 als Sicherheitsnetz

db_execute

Führt DDL-Statements aus (ALTER, CREATE, DROP, TRUNCATE).

Signatur

def db_execute(
    statement: str,
    database: str = "ki_dev",
    params: list | None = None
) -> dict

Parameter

ParameterTypDefaultBeschreibung
statementstr-DDL Statement
databasestr"ki_dev"Zieldatenbank
paramslistNoneOptional: Parameter

Erlaubte Statements

Response

{
    "status": "success",
    "statement_type": "ALTER TABLE",
    "affected_rows": 0,
    "execution_ms": 120
}

Beispiele

# Spalte hinzufügen
db_execute(
    statement="ALTER TABLE chat_sessions ADD COLUMN temperature FLOAT DEFAULT 0.7",
    database="ki_content"
)

# Index erstellen
db_execute(
    statement="CREATE INDEX idx_status ON tasks(status)",
    database="ki_dev"
)

Sicherheit

  • Statement muss mit erlaubtem Keyword beginnen
  • ExecuteValidator prüft das Statement
  • Logging in mcp_log

Fehlerbehandlung

StatusUrsacheBeispiel
deniedValidierung fehlgeschlagenWHERE fehlt, ungültiger Identifier
errorTechnischer FehlerConnection-Fehler, SQL-Syntax
successErfolgreichOperation ausgeführt

Verwandte Kapitel

MCP-DB Installation

Erstellt: 2025-12-20 | Aktualisiert: 2025-12-20

Setup, Registrierung und Test des MCP-DB Servers.

Voraussetzungen

1. Verzeichnis erstellen

mkdir -p /opt/mcp-servers/mcp-db
cd /opt/mcp-servers/mcp-db

2. Virtual Environment

python3 -m venv venv
source venv/bin/activate
pip install mcp[cli] mysql-connector-python python-dotenv

3. Dateien erstellen

Siehe Architektur für vollständige Verzeichnisstruktur.

# Struktur
/opt/mcp-servers/mcp-db/
├── server.py
├── config.py
├── .env
├── .env.example
├── requirements.txt
├── domain/
├── validators/
├── infrastructure/
└── tools/

4. .env konfigurieren

# .env erstellen
cp .env.example .env

# Bearbeiten
nano .env

# Berechtigungen setzen
chmod 600 .env

.env Inhalt

# Datenbank für Queries (mcp_readonly)
DB_HOST=localhost
DB_USER=mcp_readonly
DB_PASSWORD=SECURE_PASSWORD_HERE

# Datenbank für Logging (mcp_logger)
LOG_DB_HOST=localhost
LOG_DB_NAME=ki_protokoll
LOG_DB_USER=mcp_logger
LOG_DB_PASSWORD=DIFFERENT_SECURE_PASSWORD_HERE

5. Datenbank vorbereiten

mcp_log Tabelle

CREATE TABLE IF NOT EXISTS mcp_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    client_name VARCHAR(50) NOT NULL DEFAULT 'mcp-db',
    request TEXT,
    status ENUM('success', 'error', 'denied') NOT NULL,
    duration_ms INT DEFAULT 0,
    error_message TEXT,
    INDEX idx_timestamp (timestamp),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

DB-User erstellen

-- mcp_readonly
CREATE USER IF NOT EXISTS 'mcp_readonly'@'localhost'
IDENTIFIED BY 'SECURE_PASSWORD_HERE';

GRANT SELECT ON ki_protokoll.mcp_log TO 'mcp_readonly'@'localhost';
GRANT SELECT ON ki_protokoll.ki_eintraege TO 'mcp_readonly'@'localhost';
GRANT SELECT ON ki_protokoll.ki_kategorien TO 'mcp_readonly'@'localhost';
GRANT SELECT ON ki_protokoll.ki_tags TO 'mcp_readonly'@'localhost';
GRANT SELECT ON ki_protokoll.ki_settings TO 'mcp_readonly'@'localhost';
GRANT SELECT ON information_schema.TABLES TO 'mcp_readonly'@'localhost';
GRANT SELECT ON ki_system.* TO 'mcp_readonly'@'localhost';

-- mcp_logger
CREATE USER IF NOT EXISTS 'mcp_logger'@'localhost'
IDENTIFIED BY 'DIFFERENT_SECURE_PASSWORD_HERE';

GRANT INSERT ON ki_protokoll.mcp_log TO 'mcp_logger'@'localhost';

FLUSH PRIVILEGES;

6. Server testen

# Manuell starten
cd /opt/mcp-servers/mcp-db
source venv/bin/activate
python server.py

# Bei Erfolg: Server wartet auf stdio Input
# Strg+C zum Beenden

7. In Claude Code registrieren

User-Scope (Global)

Registrierung für alle Projekte des Benutzers:

claude mcp add mcp-db \
  --transport stdio \
  -- /opt/mcp-servers/mcp-db/venv/bin/python \
     /opt/mcp-servers/mcp-db/server.py

Projekt-Scope (Lokal)

Registrierung nur für ein bestimmtes Projekt (z.B. /var/www):

cd /var/www
claude mcp add mcp-db --transport stdio \
  -- /opt/mcp-servers/mcp-db/venv/bin/python \
     /opt/mcp-servers/mcp-db/server.py

Hinweis: Im Projekt-Scope wird die Konfiguration in /root/.claude.json unter dem entsprechenden Projekt gespeichert, nicht in .claude/settings.json im Projektverzeichnis.

8. Registrierung prüfen

# Liste aller MCP Server
claude mcp list

# Details zu mcp-db
claude mcp get mcp-db

# In Claude Code Session
/mcp

9. Tools testen

# In Claude Code:
> db_select("SELECT * FROM mcp_log LIMIT 5")
> db_schema()
> db_stats(limit=10)

Troubleshooting

Server startet nicht

# Log prüfen
tail -f /tmp/mcp_db_server.log

# Dependencies prüfen
pip list | grep mcp

# Python Version
python --version  # Muss 3.10+ sein

Verbindungsfehler

# DB-Verbindung mit MCP-DB testen (empfohlen)
db_select("SELECT 1")

# Alternativ: Direkter Test (nur für Debugging)
mysql -u mcp_readonly -p -e "SELECT 1"

# .env prüfen
cat .env | grep -v PASSWORD

Permission Denied

# Berechtigungen prüfen
ls -la .env  # Muss 600 sein

# User prüfen
mysql -u mcp_readonly -p -e "SHOW GRANTS"

Systemd Service (Optional)

MCP-DB läuft normalerweise on-demand (von Claude Code gestartet). Für Monitoring oder Debugging kann ein Systemd Service verwendet werden.

Service installiert

Der Service ist bereits unter /etc/systemd/system/mcp-db.service installiert und aktiviert.

Verwendung

# Status prüfen (zeigt on-demand oder systemd)
/opt/scripts/mcp-db-status.sh

# Service manuell starten (nicht empfohlen)
systemctl start mcp-db

# Service stoppen
systemctl stop mcp-db

# Logs ansehen
journalctl -u mcp-db -f

Wichtig

Updates

# Dependencies aktualisieren
cd /opt/mcp-servers/mcp-db
source venv/bin/activate
pip install --upgrade mcp[cli] mysql-connector-python

# Nach Updates: Service neu laden (falls aktiv)
systemctl daemon-reload
systemctl restart mcp-db  # nur falls Service läuft

Deinstallation

# Aus Claude Code entfernen
claude mcp remove mcp-db

# Verzeichnis löschen (optional)
rm -rf /opt/mcp-servers/mcp-db

# DB-User löschen (optional)
DROP USER 'mcp_readonly'@'localhost';
DROP USER 'mcp_logger'@'localhost';

Verwandte Kapitel

MCP-DB Architektur

Erstellt: 2025-12-20 | Aktualisiert: 2025-12-31

Aufbau und Struktur des MCP-DB Servers nach OOP, SRP, KISS, YAGNI Prinzipien.

Verzeichnisstruktur

/var/www/mcp-servers/mcp_db/
├── server.py              # MCP Server Entrypoint
├── config.py              # Zentrale Konfiguration
├── .env                   # Credentials (chmod 600)
├── .env.example           # Template
├── requirements.txt       # Dependencies
├── requirements-test.txt  # Test Dependencies
├── ruff.toml              # Linter Config
├── pytest.ini             # Test Config
├── run_tests.sh           # Test Runner
├── venv/                  # Virtual Environment
├── tests/                 # Unit Tests
│
├── domain/                # Contracts (Dataclasses)
│   ├── __init__.py
│   ├── query_contract.py  # QueryRequest, QueryResponse
│   └── execute_contract.py # ExecuteContract für DDL
│
├── validators/            # SRP: Separate Validierung
│   ├── __init__.py
│   ├── query_validator.py    # SELECT Validierung
│   └── execute_validator.py  # DDL Validierung (ALTER, CREATE, DROP, TRUNCATE)
│
├── infrastructure/        # Implementierungen
│   ├── __init__.py
│   └── db_connection.py   # MariaDB Connection Pool
│
└── tools/                 # MCP Tools (10 Dateien)
    ├── __init__.py
    ├── select_tool.py     # db_select
    ├── schema_tool.py     # db_schema
    ├── stats_tool.py      # db_stats
    ├── databases_tool.py  # db_databases
    ├── tables_tool.py     # db_tables
    ├── describe_tool.py   # db_describe
    ├── insert_tool.py     # db_insert
    ├── update_tool.py     # db_update
    ├── delete_tool.py     # db_delete
    └── execute_tool.py    # db_execute (DDL)

Komponenten-Diagramm

┌─────────────────────────────────────────────────────────────┐
│                     Claude Code                              │
│                         │                                    │
│                    MCP Protocol                              │
│                         │                                    │
│                         ▼                                    │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                   server.py                           │   │
│  │                 (Entrypoint)                          │   │
│  └──────────────────────┬───────────────────────────────┘   │
│                         │                                    │
│           ┌─────────────┼─────────────┐                      │
│           ▼             ▼             ▼                      │
│  ┌────────────┐ ┌────────────┐ ┌────────────┐               │
│  │ 6 Read     │ │ 3 Write    │ │ 1 DDL      │               │
│  │ Tools      │ │ Tools      │ │ Tool       │               │
│  └─────┬──────┘ └─────┬──────┘ └─────┬──────┘               │
│        │              │              │                       │
│        └──────────────┼──────────────┘                       │
│                       ▼                                      │
│  ┌──────────────────────────────────────────────────────┐   │
│  │       QueryValidator / ExecuteValidator               │   │
│  │   (Blocklist, Allowlist, Limits, DDL-Prüfung)        │   │
│  └──────────────────────┬───────────────────────────────┘   │
│                         │                                    │
│                         ▼                                    │
│  ┌────────────────────────────────────────────────────┐     │
│  │  DatabaseConnection (Connection Pool)               │     │
│  └─────────────────────┬──────────────────────────────┘     │
│                        │                                     │
│                        ▼                                     │
│           ┌────────────────────────┐                         │
│           │   MariaDB (ki_dev,     │                         │
│           │   ki_content, mcp_log) │                         │
│           └────────────────────────┘                         │
└─────────────────────────────────────────────────────────────┘

Design-Prinzipien

PrinzipUmsetzung
SRP Jede Klasse hat eine Verantwortung: Validator validiert, Tool führt aus
OOP Klassen mit klarer Kapselung, keine globalen Funktionen
KISS Minimale Komplexität, kein Over-Engineering
Immutability QueryRequest ist frozen (unveränderlich)
Fail-Safe Exceptions statt silent failures

Datenfluss

1. Claude Code ruft db_select(query, database, max_rows, params)
2. Tool empfängt Request
3. QueryValidator.validate_query() prüft:
   - SELECT-Only
   - Keyword-Blocklist
   - Database-Allowlist
   - Table-Allowlist
   - Query-Länge
   - Max-Rows
4. Bei Fehler: DENIED Response + Log
5. Bei Erfolg: Tool führt Query aus
   - Connection aus Pool
   - SET max_statement_time (MariaDB)
   - cursor.execute(query, params)
   - fetchmany(max_rows)
6. Logging in ki_dev.mcp_log
7. Response zurück an Claude Code

Validators

ValidatorPrüftVerwendet von
QueryValidator SELECT-Queries, Blocklist, Allowlist db_select
ExecuteValidator DDL-Statements (ALTER, CREATE, DROP, TRUNCATE) db_execute

Logging

Alle Operationen werden in ki_dev.mcp_log protokolliert:

Verwandte Kapitel

MCP-DB Sicherheit

Vollständige Sicherheitsmaßnahmen für 10 MCP-DB Tools.

Sicherheitsübersicht

MaßnahmeImplementierungBetrifft
Prepared Statementscursor.execute(query, params)Alle Tools
Identifier-ValidierungRegex ^[a-zA-Z0-9_]+$INSERT, UPDATE, DELETE
Mandatory WHEREwhere dict PflichtUPDATE, DELETE
Default LIMIT 100Automatisch bei DELETEDELETE
DDL Statement-ValidierungExecuteValidatordb_execute
Keyword Blocklist (15)Word Boundariesdb_select
Database Allowlistki_dev, ki_contentAlle Tools
Query Timeout30 Sekundendb_select
Row LimitMax 100db_select
Query LengthMax 2000 Zeichendb_select
LoggingJede Operation in mcp_logAlle Tools
Blocking-HookPreToolUse für Bashmysql/mariadb CLI

Schreib-Operationen Sicherheit

db_insert

SchutzImplementierung
Identifier-ValidierungTabellen- und Spaltennamen: ^[a-zA-Z0-9_]+$
Prepared StatementsWerte als Parameter, nie String-Konkatenation
DB-WhitelistNur ki_dev, ki_content erlaubt
# Sicher: Prepared Statement
INSERT INTO `tasks` (`title`, `status`) VALUES (%s, %s)
# Mit values: ("Mein Task", "pending")

db_update

UPDATE ohne WHERE ist verboten!
SchutzImplementierung
Mandatory WHERELeeres where dict → status: "denied"
Identifier-ValidierungAlle Spalten in data und where
Prepared StatementsSET und WHERE Werte als Parameter
# Erlaubt:
db_update(table="tasks", data={"status": "done"}, where={"id": 42})

# Blockiert (kein WHERE):
db_update(table="tasks", data={"status": "done"}, where={})
# → {"status": "denied", "error": "WHERE clause is required..."}

db_delete

DELETE ohne WHERE ist verboten! Default LIMIT: 100
SchutzImplementierung
Mandatory WHERELeeres where dict → status: "denied"
Default LIMITAutomatisch LIMIT 100 wenn nicht angegeben
Identifier-ValidierungSpaltennamen in where
# Erlaubt:
db_delete(table="mcp_log", where={"status": "error"}, limit=50)

# Blockiert (kein WHERE):
db_delete(table="mcp_log", where={})
# → {"status": "denied", "error": "WHERE clause is required..."}

db_execute (DDL)

SchutzImplementierung
Statement-WhitelistALTER, CREATE, DROP, TRUNCATE
ExecuteValidatorPrüft erstes Keyword
LoggingStatement-Typ in mcp_log
# Erlaubt:
db_execute("ALTER TABLE tasks ADD COLUMN priority INT")
db_execute("CREATE INDEX idx_status ON tasks(status)")
db_execute("DROP TABLE temp_data")
db_execute("TRUNCATE TABLE cache")

# Blockiert:
db_execute("SELECT * FROM tasks")  # Kein DDL
db_execute("INSERT INTO tasks...")  # Kein DDL

Identifier-Validierung

Verhindert SQL-Injection über Tabellen-/Spaltennamen:

def _validate_identifier(name: str) -> bool:
    """Validiert Tabellen-/Spaltennamen gegen SQL-Injection."""
    return bool(re.match(r"^[a-zA-Z0-9_]+$", name))
EingabeValideGrund
tasksJaAlphanumerisch
chat_sessionsJaMit Underscore
tasks; DROP TABLE--NeinSonderzeichen
tasks`NeinBacktick

Blocking-Hook (PreToolUse)

KRITISCH: Direkte mysql/mariadb-Befehle werden auf System-Ebene blockiert!

Blockierte Muster

# Blockierte Befehle (Beispiele)
mysql -u root -pPassword123 -e "SELECT 1"
mariadb -u user -pSecret
mysql --password=geheim --execute="DROP TABLE users"
mysqldump ki_protokoll > backup.sql
sudo mysql -u root -pPass

# Stattdessen MCP-DB verwenden:
db_select      - SELECT-Abfragen
db_insert      - Datensätze einfügen
db_update      - Datensätze ändern
db_delete      - Datensätze löschen
db_execute     - DDL (ALTER/CREATE/DROP)
db_describe    - Tabellenstruktur
db_databases   - Datenbanken auflisten
db_tables      - Tabellen auflisten
db_schema      - Tabellen-Metadaten
db_stats       - MCP-Log Statistiken

Hook-Konfiguration

Pfad: /var/www/.claude/settings.local.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/var/www/tools/ki-protokoll/claude-hook/block_direct_db.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Sicherheitsschichten (Defense in Depth)

SchichtMechanismusSchutz vor
1. Claude Code Hook block_direct_db.py (Exit Code 2) Direkte Bash-Befehle mit mysql/mariadb
2. Identifier-Validierung Regex ^[a-zA-Z0-9_]+$ SQL-Injection über Namen
3. Mandatory WHERE where dict Pflicht Versehentliches UPDATE/DELETE aller Zeilen
4. Default LIMIT LIMIT 100 bei DELETE Massenlöschung
5. DDL-Whitelist ExecuteValidator Unerlaubte DDL-Statements
6. Prepared Statements cursor.execute(sql, params) SQL-Injection über Werte
7. DB-Whitelist Config.ALLOWED_DATABASES Zugriff auf andere Datenbanken
8. Audit-Log mcp_log Tabelle Nachvollziehbarkeit, Incident Response

Keyword Blocklist (db_select)

KategorieKeywordsRisiko
DMLDELETE, INSERT, UPDATEDatenmanipulation
DDLDROP, TRUNCATE, ALTER, CREATE, RENAMEStrukturänderung
DCLGRANT, REVOKERechteänderung
File-OpsLOAD_FILE, INTO OUTFILE, INTO DUMPFILEDateizugriff
DoSBENCHMARK, SLEEPResource Exhaustion

Incident Response

EventIndikatorMaßnahme
Hook blockiert Befehl PreToolUse:Bash hook error: BLOCKIERT MCP-DB Tools verwenden
WHERE fehlt status='denied', "WHERE clause is required" where Parameter hinzufügen
Ungültiger Identifier status='denied', "Invalid column name" Spaltenname prüfen
Unerlaubtes DDL status='denied', "Statement not allowed" Nur ALTER/CREATE/DROP/TRUNCATE
SQL Injection Versuch status='denied', Keyword in error_message Log analysieren

Credential-Management

.env Datei

# Berechtigungen setzen
chmod 600 /opt/mcp-servers/mcp-db/.env
chown root:root /opt/mcp-servers/mcp-db/.env

Keine Credentials in Claude Config

# Registrierung OHNE Credentials
claude mcp add mcp-db \
  --transport stdio \
  -- /opt/mcp-servers/mcp-db/venv/bin/python \
     /opt/mcp-servers/mcp-db/server.py

# Credentials werden aus .env geladen

Verwandte Kapitel

MCP-DB Konfiguration

Konfiguration mit Hot-Reload aus JSON-Datei - Änderungen werden sofort wirksam ohne Neustart.

Übersicht

BereichDateiHot-ReloadBeschreibung
JSON-Config/etc/mcp-db/config.jsonJaAllowlists, Limits, Keywords
Environment/var/www/mcp-servers/mcp_db/.envNeinDB-Credentials
Claude Hooks/root/.claude/settings.jsonNeinBlocking-Hook

Neu: Änderungen an /etc/mcp-db/config.json werden bei jedem Tool-Aufruf automatisch geladen - kein Claude-Neustart nötig!

JSON-Konfiguration (Hot-Reload)

Pfad: /etc/mcp-db/config.json

{
  "_comment": "MCP-DB Konfiguration - Änderungen werden sofort wirksam",

  "allowed_databases": [
    "ki_dev",
    "ki_content"
  ],

  "allowed_tables": {
    "ki_dev": [
      "mcp_log", "protokoll", "file_backup_history",
      "tasks", "task_assignments", "task_results", "task_comments",
      "contracts", "contract_history", "contract_validations",
      "dokumentation", "dokumentation_chunks", "dokumentation_history",
      "pipeline_log", "pipeline_queue", "llm_requests", "rag_collections",
      "ai_models", "audit_log", "code_analysis", "code_dependencies",
      "code_quality", "code_scan_config", "prompts"
    ],
    "ki_content": [
      "content_orders", "content_versions", "content_critiques",
      "content_sources", "content_config", "content_config_history",
      "documents", "chunks", "chunk_semantics", "chunk_entities",
      "chunk_taxonomy", "document_taxonomy", "document_entities",
      "document_pages", "entity_semantics", "entity_taxonomy_mapping",
      "entity_types", "generated_questions",
      "pipeline_configs", "pipeline_queue", "pipeline_runs", "pipeline_steps",
      "provenance", "semantic_queue", "stopwords",
      "chat_sessions", "chat_messages",
      "entities", "entity_relations", "entity_classifications",
      "taxonomy_terms", "ontology_classes", "search_history"
    ]
  },

  "blocked_keywords": [
    "DROP", "DELETE", "INSERT", "UPDATE", "TRUNCATE",
    "ALTER", "CREATE", "RENAME", "GRANT", "REVOKE",
    "LOAD_FILE", "INTO OUTFILE", "INTO DUMPFILE",
    "BENCHMARK", "SLEEP"
  ],

  "limits": {
    "max_query_length": 2000,
    "max_rows": 100,
    "query_timeout_sec": 30
  }
}

Datenbank hinzufügen

# /etc/mcp-db/config.json bearbeiten
{
  "allowed_databases": [
    "ki_dev",
    "ki_content",
    "neue_datenbank"  // NEU
  ],
  "allowed_tables": {
    // ...
    "neue_datenbank": [  // NEU
      "tabelle1",
      "tabelle2"
    ]
  }
}

# Sofort wirksam - kein Neustart nötig!

Environment Variables (.env)

Pfad: /var/www/mcp-servers/mcp_db/.env

Änderungen an .env erfordern einen Claude-Neustart!

VariableBeschreibungDefault
DB_HOSTDatenbank-Host für Querieslocalhost
DB_USERUser für Queriesmcp_readonly
DB_PASSWORDPasswort für Query-User-
LOG_DB_HOSTDatenbank-Host für Logginglocalhost
LOG_DB_NAMEDatenbank für Loggingki_dev
LOG_DB_USERUser für Logging (nur INSERT)mcp_logger
LOG_DB_PASSWORDPasswort für Logger-User-

Claude Code Hook

Der Blocking-Hook verhindert direkte mysql/mariadb-Befehle.

Pfad: /root/.claude/settings.json

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "/var/www/scripts/hooks/block_direct_db.py",
        "timeout": 5
      }]
    }]
  }
}

Exit-Codes

Exit CodeBedeutungAktion
0ErlaubenBefehl wird ausgeführt
2BlockierenBefehl wird abgelehnt

config.py Implementierung

Die Config-Klasse lädt bei jedem Zugriff die JSON-Datei neu (Metaclass mit Properties):

"""config.py - Hot-Reload aus JSON"""
import json
from pathlib import Path

CONFIG_PATH = Path("/etc/mcp-db/config.json")

def _load_json_config() -> dict:
    """Lädt JSON-Config bei jedem Aufruf."""
    if CONFIG_PATH.exists():
        with open(CONFIG_PATH, "r") as f:
            return json.load(f)
    return {}

class _ConfigMeta(type):
    @property
    def ALLOWED_DATABASES(cls) -> list:
        cfg = _load_json_config()
        return cfg.get("allowed_databases", ["ki_dev", "ki_content"])

    @property
    def ALLOWED_TABLES(cls) -> list:
        cfg = _load_json_config()
        tables_dict = cfg.get("allowed_tables", {})
        return [t for tables in tables_dict.values() 
                if isinstance(tables, list) for t in tables]
    # ... weitere Properties

class Config(metaclass=_ConfigMeta):
    # Statische Werte aus Environment
    DB_HOST = os.getenv("DB_HOST", "localhost")
    DB_USER = os.getenv("DB_USER", "mcp_readonly")
    # ...

Keyword Blocklist

KategorieKeywords
DMLDELETE, INSERT, UPDATE
DDLDROP, TRUNCATE, ALTER, CREATE, RENAME
DCLGRANT, REVOKE
File-OpsLOAD_FILE, INTO OUTFILE, INTO DUMPFILE
DoSBENCHMARK, SLEEP

Limits

LimitWertBeschreibung
max_query_length2000Maximale Query-Länge in Zeichen
max_rows100Maximale Ergebniszeilen
query_timeout_sec30Query-Timeout in Sekunden

Verwandte Kapitel

MCP-DB Contracts

Typisierte Datenstrukturen für Request und Response.

QueryRequest

Immutable Request-Objekt für Datenbankabfragen.

from dataclasses import dataclass
from typing import Optional

@dataclass(frozen=True)
class QueryRequest:
    """Immutable Query Request - SRP: Nur Query-Daten"""
    query: str
    params: Optional[tuple] = None
    database: str = "ki_dev"
    max_rows: int = 100
FeldTypDefaultBeschreibung
querystr-SQL SELECT Statement
paramsOptional[tuple]NonePrepared Statement Parameter
databasestr"ki_dev"Zieldatenbank
max_rowsint100Maximale Ergebniszeilen

QueryStatus

Enum für Query-Ergebnisse.

from enum import Enum

class QueryStatus(Enum):
    SUCCESS = "success"
    ERROR = "error"
    DENIED = "denied"
StatusBedeutung
SUCCESSQuery erfolgreich ausgeführt
ERRORTechnischer Fehler bei Ausführung
DENIEDQuery durch Validator abgelehnt

QueryResponse

Strukturierte Antwort auf Datenbankabfragen.

@dataclass
class QueryResponse:
    """Structured Response - SRP: Nur Response-Daten"""
    status: QueryStatus
    data: Optional[List[dict]] = None
    row_count: int = 0
    error: Optional[str] = None
    execution_ms: float = 0.0
FeldTypBeschreibung
statusQueryStatusErgebnis-Status
dataOptional[List[dict]]Ergebniszeilen
row_countintAnzahl Zeilen
errorOptional[str]Fehlermeldung (gekürzt)
execution_msfloatAusführungszeit in ms

ExecuteContract

Contract für DDL-Operationen (db_execute Tool).

@dataclass(frozen=True)
class ExecuteRequest:
    """Request für DDL-Statements"""
    statement: str
    database: str = "ki_dev"
    params: Optional[tuple] = None

@dataclass
class ExecuteResponse:
    """Response für DDL-Statements"""
    status: str  # success, error, denied
    affected_rows: int = 0
    error: Optional[str] = None
    execution_ms: float = 0.0

Beispiel-Nutzung

# Request erstellen
request = QueryRequest(
    query="SELECT * FROM mcp_log WHERE status = %s",
    params=("success",),
    database="ki_dev",
    max_rows=50
)

# Response auswerten
if response.status == QueryStatus.SUCCESS:
    for row in response.data:
        print(row)
else:
    print(f"Fehler: {response.error}")

Logging

Alle Operationen werden direkt in ki_dev.mcp_log protokolliert. Die Logging-Struktur entspricht der mcp_log Tabelle:

FeldTypBeschreibung
client_nameVARCHAR(50)Client-Identifikation (mcp-db)
requestTEXTQuery-Text (gekürzt)
statusENUMsuccess, error, denied
duration_msINTAusführungsdauer
error_messageTEXTFehlerdetails (optional)

Verwandte Kapitel

MCP-DB Validators

SQL-Validierung nach dem Single Responsibility Principle.

QueryValidator

Validiert SELECT-Queries für das db_select Tool.

"""SRP: Separate Validierungslogik"""
from typing import Tuple
import re
from config import Config

class QueryValidator:
    """Validiert SQL Queries - SRP: Nur Validierung"""

    @staticmethod
    def validate_query(query: str, database: str, max_rows: int) -> Tuple[bool, str]:
        """
        Validiert eine Query gegen alle Sicherheitsregeln.

        Returns:
            (is_valid, error_message)
        """
        # Basis-Validierung
        if not query or len(query) < 1:
            return False, "Query must not be empty"

        if len(query) > Config.MAX_QUERY_LENGTH:
            return False, f"Query must be max {Config.MAX_QUERY_LENGTH} chars"

        # Nur SELECT erlaubt
        query_upper = query.strip().upper()
        if not query_upper.startswith("SELECT"):
            return False, "Only SELECT queries allowed"

        # Dangerous Keyword Blocklist
        for keyword in Config.BLOCKED_KEYWORDS:
            pattern = r'\b' + re.escape(keyword) + r'\b'
            if re.search(pattern, query_upper):
                return False, f"Blocked keyword detected: {keyword}"

        # Database Allowlist
        if database not in Config.ALLOWED_DATABASES:
            return False, f"Database '{database}' not allowed"

        # Max Rows prüfen
        if max_rows < 1 or max_rows > Config.MAX_ROWS:
            return False, f"max_rows must be 1-{Config.MAX_ROWS}"

        # Table Allowlist
        from_tables = QueryValidator._extract_table_names(query_upper)
        for table in from_tables:
            if "INFORMATION_SCHEMA" in table:
                continue
            table_clean = table.split('.')[-1]
            if table_clean not in [t.upper() for t in Config.ALLOWED_TABLES]:
                return False, f"Table '{table}' not allowed"

        return True, ""

Validierungsschritte (QueryValidator)

#PrüfungFehler bei
1Query nicht leerLeere Query
2Query-Länge> 2000 Zeichen
3SELECT-OnlyNicht mit SELECT beginnend
4Keyword-BlocklistBlockiertes Keyword gefunden
5Database-AllowlistDatenbank nicht erlaubt
6Max-Rows< 1 oder > 100
7Table-AllowlistTabelle nicht erlaubt

ExecuteValidator

Validiert DDL-Statements für das db_execute Tool.

"""Execute Validator - Validierung für DDL/DML Statements."""
import re
from config import Config

class ExecuteValidator:
    """Validiert DDL/DML Statements gegen Sicherheitsregeln."""

    # Erlaubte Statement-Typen
    ALLOWED_STATEMENTS: list[str] = ["ALTER", "CREATE", "DROP", "TRUNCATE", "SET"]

    # Erlaubte SET-Variablen (Whitelist)
    ALLOWED_SET_VARS: list[str] = ["FOREIGN_KEY_CHECKS"]

    # Verbotene Patterns (kritisch!)
    FORBIDDEN_PATTERNS: list[str] = [
        r"\bDROP\s+DATABASE\b",
        r"\bDROP\s+SCHEMA\b",
        r"\bCREATE\s+DATABASE\b",
        r"\bCREATE\s+SCHEMA\b",
        r"\bGRANT\b",
        r"\bREVOKE\b",
        r"\bSHUTDOWN\b",
    ]

    # Verbotene Datenbanken (System-DBs)
    FORBIDDEN_DATABASES: list[str] = [
        "information_schema",
        "mysql",
        "performance_schema",
        "sys",
    ]

    @classmethod
    def validate_statement(cls, statement: str, database: str) -> tuple[bool, str]:
        """
        Validiert ein DDL/DML Statement.

        Returns:
            Tuple (is_valid, error_message)
        """
        if not statement or len(statement.strip()) < 5:
            return False, "Statement must not be empty"

        if len(statement) > Config.MAX_QUERY_LENGTH:
            return False, f"Statement exceeds max length"

        statement_upper = statement.strip().upper()

        # Prüfe ob Statement mit erlaubtem Keyword beginnt
        statement_type = cls._get_statement_type(statement_upper)
        if statement_type is None:
            return False, f"Statement type not allowed. Allowed: {', '.join(cls.ALLOWED_STATEMENTS)}"

        # Bei SET: nur erlaubte Variablen
        if statement_type == "SET":
            allowed = any(var in statement_upper for var in cls.ALLOWED_SET_VARS)
            if not allowed:
                return False, f"SET variable not allowed. Allowed: {', '.join(cls.ALLOWED_SET_VARS)}"

        # Prüfe verbotene Patterns
        for pattern in cls.FORBIDDEN_PATTERNS:
            if re.search(pattern, statement_upper):
                return False, f"Forbidden pattern detected"

        # Prüfe Datenbank
        if database not in Config.ALLOWED_DATABASES:
            return False, f"Database '{database}' not allowed"

        # Prüfe ob Statement gegen System-DB zielt
        for forbidden_db in cls.FORBIDDEN_DATABASES:
            if re.search(rf"\b{forbidden_db}\b", statement_upper, re.IGNORECASE):
                return False, f"Operations on '{forbidden_db}' are forbidden"

        return True, ""

Validierungsschritte (ExecuteValidator)

#PrüfungFehler bei
1Statement nicht leerLeeres Statement
2Statement-Länge> 2000 Zeichen
3Statement-TypNicht ALTER/CREATE/DROP/TRUNCATE/SET
4SET-VariablenNicht in ALLOWED_SET_VARS
5Forbidden PatternsDROP DATABASE, GRANT, etc.
6Database-AllowlistDatenbank nicht erlaubt
7System-DB-Schutzmysql, information_schema, etc.

Erlaubte DDL-Statements

TypBeispielStatus
ALTER TABLEALTER TABLE tasks ADD COLUMN priority INTErlaubt
CREATE TABLECREATE TABLE temp_data (...)Erlaubt
CREATE INDEXCREATE INDEX idx_name ON table(col)Erlaubt
DROP TABLEDROP TABLE temp_dataErlaubt
DROP INDEXDROP INDEX idx_name ON tableErlaubt
TRUNCATETRUNCATE TABLE temp_dataErlaubt
SETSET FOREIGN_KEY_CHECKS = 0Erlaubt

Verbotene Patterns

PatternBeschreibung
DROP DATABASEDatenbank-Löschung verboten
DROP SCHEMASchema-Löschung verboten
CREATE DATABASEDatenbank-Erstellung verboten
CREATE SCHEMASchema-Erstellung verboten
GRANTRechte-Vergabe verboten
REVOKERechte-Entzug verboten
SHUTDOWNServer-Shutdown verboten

Keyword-Erkennung

Keywords werden mit Word Boundaries erkannt:

# Regex-Pattern für Keyword "DROP"
pattern = r'\bDROP\b'

# Blockiert:
"SELECT * FROM t; DROP TABLE t"  # DROP erkannt

# Nicht blockiert (kein vollständiges Wort):
"SELECT dropdown FROM t"         # DROP nicht erkannt

Testfälle

# QueryValidator - Muss DENIED returnen:
"SELECT * FROM mcp_log; DROP TABLE mcp_log"  # DROP
"SELECT LOAD_FILE('/etc/passwd')"            # LOAD_FILE
"SELECT * FROM users"                        # users nicht erlaubt
"SELECT SLEEP(10)"                           # SLEEP

# ExecuteValidator - Muss DENIED returnen:
"DROP DATABASE ki_dev"                       # Forbidden Pattern
"GRANT ALL ON ki_dev.* TO user"              # GRANT verboten
"ALTER TABLE mysql.user ADD COLUMN x INT"   # System-DB

# ExecuteValidator - Muss SUCCESS returnen:
"ALTER TABLE tasks ADD COLUMN priority INT"  # Erlaubt
"CREATE INDEX idx_status ON tasks(status)"   # Erlaubt
"SET FOREIGN_KEY_CHECKS = 0"                 # Erlaubte Variable

Verwandte Kapitel

MCP-DB Infrastructure

Database Connection Pool für sichere Datenbankzugriffe.

DatabaseConnection

Singleton Connection Pool für Datenbankzugriffe.

"""Database Connection Pool"""
from contextlib import contextmanager
import mysql.connector
from mysql.connector import pooling
from config import Config

class DatabaseConnection:
    """Singleton Connection Pool - SRP: Nur Verbindungsmanagement"""

    _pool = None

    @classmethod
    def get_pool(cls):
        """Erstellt oder gibt existierenden Connection Pool zurück"""
        if cls._pool is None:
            cls._pool = pooling.MySQLConnectionPool(
                pool_name="mcp_pool",
                pool_size=5,
                host=Config.DB_HOST,
                user=Config.DB_USER,
                password=Config.DB_PASSWORD,
                charset="utf8mb4",
                connection_timeout=10,
                autocommit=True,
            )
        return cls._pool

    @classmethod
    @contextmanager
    def get_connection(cls, database: str):
        """
        Context Manager für DB Connection.

        Args:
            database: Datenbankname (bereits validiert)
        """
        conn = cls.get_pool().get_connection()
        try:
            # Datenbank wechseln (USE statement)
            cursor = conn.cursor()
            cursor.execute(f"USE {database}")

            # Query Timeout setzen (MariaDB: max_statement_time in Sekunden)
            try:
                cursor.execute(
                    f"SET SESSION max_statement_time = {Config.QUERY_TIMEOUT_SEC}"
                )
            except Exception:
                pass  # Falls Variable nicht unterstützt wird
            cursor.close()

            yield conn
        finally:
            conn.close()

Pool-Konfiguration

ParameterWertBeschreibung
pool_size5Maximale Verbindungen
connection_timeout10Verbindungs-Timeout in Sekunden
autocommitTrueAutomatisches Commit
charsetutf8mb4Unicode-Support

Query Timeout

Bei jeder Verbindung wird max_statement_time gesetzt (MariaDB-spezifisch):

SET SESSION max_statement_time = 30  # 30 Sekunden

Hinweis: MySQL verwendet max_execution_time in Millisekunden, MariaDB verwendet max_statement_time in Sekunden.

Logging

Alle MCP-DB Operationen werden direkt in ki_dev.mcp_log protokolliert. Das Logging erfolgt inline in den Tool-Implementierungen, nicht über eine separate Logger-Klasse.

Logging-Ablauf

  1. Tool wird aufgerufen (z.B. db_select)
  2. Query wird validiert
  3. Query wird ausgeführt
  4. Ergebnis wird in mcp_log geschrieben

mcp_log Tabelle

CREATE TABLE IF NOT EXISTS mcp_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    client_name VARCHAR(50) NOT NULL DEFAULT 'mcp-db',
    request TEXT,
    status ENUM('success', 'error', 'denied') NOT NULL,
    duration_ms INT DEFAULT 0,
    error_message TEXT,
    INDEX idx_timestamp (timestamp),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Log-Felder

FeldTypBeschreibung
timestampDATETIMEZeitstempel der Operation
client_nameVARCHAR(50)Client-Identifikation (mcp-db)
requestTEXTQuery-Text (gekürzt auf 200 Zeichen)
statusENUMsuccess, error, denied
duration_msINTAusführungsdauer in Millisekunden
error_messageTEXTFehlerdetails (bei Fehlern)

Nutzung

# DatabaseConnection mit Context Manager
with DatabaseConnection.get_connection("ki_dev") as conn:
    cursor = conn.cursor(dictionary=True)
    cursor.execute("SELECT * FROM mcp_log LIMIT 10")
    rows = cursor.fetchall()
    cursor.close()

# Log-Eintrag schreiben (inline in Tool)
cursor.execute(
    """INSERT INTO mcp_log
       (client_name, request, status, duration_ms, error_message)
       VALUES (%s, %s, %s, %s, %s)""",
    ("mcp-db", query[:200], "success", duration_ms, None)
)

Verzeichnisstruktur

/var/www/mcp-servers/mcp_db/infrastructure/
├── __init__.py
└── db_connection.py    # DatabaseConnection Klasse

Verwandte Kapitel

MCP-DB Testprotokoll

Vollständiger Funktionstest aller MCP-DB Features.

Testübersicht

Kategorie Tests Status
db_select/basic 6 100%
db_select/params 3 100%
db_select/db_allow 4 100%
db_select/table_allow 8 100%
db_select/blocklist 17 100%
db_select/limits 8 100%
db_schema 3 100%
db_stats 5 100%

Testmatrix: db_select

Basis-Funktionalität

TestQueryErwartungStatus
Simple SELECTSELECT 1 as testsuccessOK
SELECT with columnsSELECT id, status FROM mcp_logsuccessOK
SELECT with WHERESELECT * FROM mcp_log WHERE status = 'success'successOK
SELECT with ORDER BYSELECT * FROM mcp_log ORDER BY id DESCsuccessOK
SELECT with COUNTSELECT COUNT(*) as total FROM mcp_logsuccessOK
SELECT with GROUP BYSELECT status, COUNT(*) FROM mcp_log GROUP BY statussuccessOK

Prepared Statements

TestQueryParamsStatus
Single paramSELECT * FROM mcp_log WHERE status = %s["success"]OK
Multiple paramsSELECT * FROM mcp_log WHERE status = %s AND id > %s["success", 0]OK
LIKE paramSELECT * FROM mcp_log WHERE request LIKE %s["%SELECT%"]OK

Database Allowlist

DatabaseErwartungStatus
ki_devallowedOK
ki_contentallowedOK
mysqldeniedOK
information_schemadeniedOK

Table Allowlist

TabelleDatabaseErwartungStatus
mcp_logki_devallowedOK
taskski_devallowedOK
documentski_contentallowedOK
chunkski_contentallowedOK
chat_sessionski_contentallowedOK
users-deniedOK
secrets-deniedOK
mysql.user-deniedOK

Keyword Blocklist (15 Keywords)

KategorieKeywordTest-QueryStatus
DMLDROPSELECT ...; DROP TABLE xBLOCKED
DMLDELETESELECT ...; DELETE FROM xBLOCKED
DMLINSERTSELECT ...; INSERT INTO xBLOCKED
DMLUPDATESELECT ...; UPDATE x SETBLOCKED
DDLTRUNCATESELECT ...; TRUNCATE TABLEBLOCKED
DDLALTERSELECT ...; ALTER TABLEBLOCKED
DDLCREATESELECT ...; CREATE TABLEBLOCKED
DDLRENAMESELECT ...; RENAME TABLEBLOCKED
DCLGRANTSELECT ...; GRANT ALLBLOCKED
DCLREVOKESELECT ...; REVOKE ALLBLOCKED
FileLOAD_FILESELECT LOAD_FILE('/etc/passwd')BLOCKED
FileINTO OUTFILESELECT ... INTO OUTFILEBLOCKED
FileINTO DUMPFILESELECT ... INTO DUMPFILEBLOCKED
DoSBENCHMARKSELECT BENCHMARK(...)BLOCKED
DoSSLEEPSELECT SLEEP(10)BLOCKED

Word Boundary Tests

TestQueryErwartungStatus
dropdown != DROPSELECT 'dropdown' as wordsuccessOK
updated_at != UPDATESELECT 'updated_at' as colsuccessOK

Limits

TestParameterErwartungStatus
max_rows=1max_rows=1successOK
max_rows=50max_rows=50successOK
max_rows=100max_rows=100success (max)OK
max_rows=0max_rows=0deniedOK
max_rows=101max_rows=101denied (>100)OK
Empty queryquery=""deniedOK
Non-SELECTquery="SHOW TABLES"deniedOK
Query > 2000 charsquery=long_stringdeniedOK

Testmatrix: db_schema

TestDatabaseErwartungStatus
ki_dev schemaki_devtables[]OK
ki_content schemaki_contenttables[]OK
mysql deniedmysqlerrorOK

Testmatrix: db_stats

TestParameterErwartungStatus
Default limitlimit=50logs[]OK
Custom limitlimit=5logs[] (5)OK
Max limitlimit=100logs[] (max 100)OK
Clamped minlimit=0logs[] (clamped to 1)OK
Clamped maxlimit=200logs[] (clamped to 100)OK

Konfiguration

ParameterWert
Erlaubte Datenbankenki_dev, ki_content
Erlaubte Tabellen (ki_dev)23
Erlaubte Tabellen (ki_content)35
Blockierte Keywords15
Max Query Length2000 Zeichen
Max Rows100
Query Timeout30 Sekunden

Test ausführen

# Comprehensive Test Suite
cd /var/www/mcp-servers/mcp_db
source venv/bin/activate
python comprehensive_test.py

# Erwartete Ausgabe:
# STATUS: ALLE TESTS BESTANDEN

Letzte Prüfung

DatumVersionErgebnis
2025-12-31Python 3.13Bestanden

Verwandte Kapitel

MCP-DB Cheat-Sheet

Schnellreferenz für den Umstieg von direkten mysql-Befehlen auf MCP-DB Tools.

WICHTIG: mysql/mariadb-Befehle sind blockiert!

Direkte Datenbankzugriffe mit mysql oder mariadb werden durch einen PreToolUse Hook automatisch blockiert.

# Diese Befehle werden BLOCKIERT:
mysql -u root -pPassword -e "SELECT 1"
mariadb -u user -pSecret
mysqldump ki_protokoll > backup.sql

# Stattdessen MCP-DB verwenden (10 Tools verfügbar)

MCP-DB Tools Übersicht (10 Tools)

ToolVerwendungParameter
Lese-Operationen
db_selectSELECT Queries ausführenquery, database, max_rows, params
db_schemaTabellen-Metadatendatabase
db_statsMCP-Log Einträgelimit
db_databasesErlaubte DBs auflisten-
db_tablesTabellen auflistendatabase, include_row_count
db_describeTabellenstrukturtable, database, show_create
Schreib-Operationen
db_insertDatensatz einfügentable, data, database
db_updateDatensätze änderntable, data, where, database
db_deleteDatensätze löschentable, where, database, limit
DDL-Operationen
db_executeALTER/CREATE/DROP/TRUNCATEstatement, database, params

SELECT Queries

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -u root -p ki_dev \
  -e "SELECT * FROM tasks LIMIT 5"
db_select(
    "SELECT * FROM tasks LIMIT 5",
    database="ki_dev"
)

Schema-Inspektion

Datenbanken auflisten

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "SHOW DATABASES"
db_databases()

Tabellen auflisten

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "SHOW TABLES FROM ki_dev"
db_tables(database="ki_dev")

Tabellenstruktur anzeigen

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "DESCRIBE ki_dev.tasks"
db_describe(
    table="tasks",
    database="ki_dev"
)
mysql -e "SHOW CREATE TABLE tasks"
db_describe(
    table="tasks",
    database="ki_dev",
    show_create=True
)

INSERT

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "INSERT INTO tasks 
  (title, status) 
  VALUES ('Test', 'pending')"
db_insert(
    table="tasks",
    data={
        "title": "Test",
        "status": "pending"
    },
    database="ki_dev"
)

UPDATE

WHERE ist Pflicht! UPDATE ohne WHERE wird abgelehnt.
Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "UPDATE tasks 
  SET status='completed' 
  WHERE id=42"
db_update(
    table="tasks",
    data={"status": "completed"},
    where={"id": 42},
    database="ki_dev"
)

DELETE

WHERE ist Pflicht! Default LIMIT: 100 als Sicherheit.
Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "DELETE FROM mcp_log 
  WHERE status='error' 
  LIMIT 10"
db_delete(
    table="mcp_log",
    where={"status": "error"},
    database="ki_protokoll",
    limit=10
)

DDL (ALTER/CREATE/DROP)

Alt (BLOCKIERT)Neu (MCP-DB)
mysql -e "ALTER TABLE tasks 
  ADD COLUMN priority INT"
db_execute(
    statement="ALTER TABLE tasks ADD COLUMN priority INT",
    database="ki_dev"
)
mysql -e "CREATE INDEX idx_status 
  ON tasks(status)"
db_execute(
    statement="CREATE INDEX idx_status ON tasks(status)",
    database="ki_dev"
)
mysql -e "DROP TABLE temp_data"
db_execute(
    statement="DROP TABLE temp_data",
    database="ki_dev"
)

Häufige Anwendungsfälle

Task erstellen und Status ändern

# Task erstellen
result = db_insert(
    table="tasks",
    data={
        "title": "Neue Aufgabe",
        "description": "Beschreibung",
        "status": "pending",
        "type": "ai_task"
    },
    database="ki_dev"
)
task_id = result["inserted_id"]

# Status ändern
db_update(
    table="tasks",
    data={"status": "in_progress"},
    where={"id": task_id},
    database="ki_dev"
)

Spalte zu Tabelle hinzufügen

# Aktuelle Struktur prüfen
db_describe(table="chat_sessions", database="ki_content")

# Spalte hinzufügen
db_execute(
    statement="ALTER TABLE chat_sessions ADD COLUMN temperature FLOAT DEFAULT 0.7",
    database="ki_content"
)

# Verifizieren
db_describe(table="chat_sessions", database="ki_content")

Alte Logs aufräumen

# Anzahl prüfen
db_select(
    "SELECT COUNT(*) as cnt FROM mcp_log WHERE status='error'",
    database="ki_protokoll"
)

# Löschen (max 100 pro Aufruf)
db_delete(
    table="mcp_log",
    where={"status": "error"},
    database="ki_protokoll",
    limit=100
)

Datenbank-Übersicht

DatenbankZweckWichtige Tabellen
ki_devDevelopmenttasks, contracts, dokumentation, mcp_log
ki_contentContentchat_sessions, chat_messages, content, personas

Sicherheitsregeln

RegelToolVerhalten
WHERE Pflichtdb_update, db_deleteAbgelehnt ohne WHERE
Default LIMIT 100db_deleteMax 100 Zeilen pro Aufruf
Identifier-Validierungdb_insert, db_update, db_deleteRegex: ^[a-zA-Z0-9_]+$
DDL-Whitelistdb_executeNur ALTER/CREATE/DROP/TRUNCATE
DB-WhitelistAlleNur ki_dev, ki_content

Fehlerbehandlung

StatusBedeutungAktion
successOperation erfolgreich-
deniedValidierung fehlgeschlagenParameter prüfen
errorTechnischer FehlerSQL-Syntax, Connection prüfen

Verwandte Kapitel