MariaDB-MCP
# MariaDB MCP Server (JavaScript)
Servidor MCP escrito en **Node.js / JavaScript** para interactuar con bases de datos **MariaDB/MySQL**.
## Características
- **Transporte stdio** compatible con el protocolo MCP (Model Context Protocol).
- **Transporte HTTP/SSE** preparado para extenderse con un bridge web.
- **Conexión pool** usando `mysql2/promise` con tamaño configurable.
- **Soporte SSL** con CA, certificados cliente y configuración de verificación.
- **Modo solo lectura (READ_ONLY)** que bloquea queries de escritura y funciones de archivo (`LOAD_FILE`, `INTO OUTFILE`).
- **Límites de ejecución**: tiempo máximo por consulta (`MCP_QUERY_TIMEOUT`) y tope de filas devueltas (`MCP_MAX_ROWS`), con aviso explícito cuando un resultado se recorta.
- **Autenticación por API key** en el transporte HTTP (`MCP_API_KEY`), con comparación en tiempo constante.
- **Logging** a `stderr` (compatible con stdio transport) y archivos rotados diariamente con `winston`.
- **Docker y Docker Compose** listos para usar.
## Herramientas MCP expuestas
| Herramienta | Descripción |
|-------------|-------------|
| `list_databases` | Lista todas las bases de datos accesibles. |
| `list_tables` | Lista las tablas de una base de datos. |
| `get_table_schema` | Obtiene columnas, tipos, nullable, keys, defaults y extras. |
| `get_table_schema_with_relations` | Igual que `get_table_schema` pero incluye claves foráneas. |
| `execute_sql` | Ejecuta queries `SELECT/SHOW/DESCRIBE`. Valida parámetros, modo solo lectura, tiempo máximo y tope de filas. |
| `create_database` | Crea una base de datos (requiere `MCP_READ_ONLY=false`). |
### Selección de base de datos
Todas las herramientas que trabajan sobre una base de datos aceptan el parámetro
`database_name` **y sus alias `database`, `db` y `schema`** (útil para clientes que
renombran el parámetro). La conexión se cambia explícitamente con `changeUser()` y
se verifica con `SELECT DATABASE()` antes de ejecutar la query, de modo que la
consulta **siempre** corre sobre la base de datos indicada en la herramienta y nunca
sobre la de una llamada anterior.
- `execute_sql`: si no se envía `database_name`, se usa `DB_NAME` (la configurada).
El servidor avisa en el log cuando se ejecuta sin base de datos explícita.
- El resto de herramientas exigen `database_name` y devuelven un error claro con los
alias aceptados si falta.
### Límites de ejecución
- **Tiempo máximo por consulta** (`MCP_QUERY_TIMEOUT`, 30 s por defecto): se aplica con
`SET SESSION max_statement_time` (MariaDB). Si el servidor no lo admite se intenta
`max_execution_time` (MySQL, en milisegundos y solo para `SELECT`); si no hay ninguno
disponible, el límite se desactiva y se avisa una sola vez en el log, en lugar de
romper todas las consultas.
- Cuando una consulta agota el tiempo, el servidor la cancela y devuelve un error claro
(`MCP_QUERY_TIMEOUT=...`). La conexión sigue siendo válida y vuelve al pool, así que un
`SELECT` desbocado no bloquea el servidor ni contamina las llamadas siguientes.
- **Tope de filas** (`MCP_MAX_ROWS`, 1000 por defecto): lo aplica el propio servidor con
`sql_select_limit = MCP_MAX_ROWS + 1`, de modo que un resultado gigante **no viaja por
la red**. Al recortar, `execute_sql` responde así en lugar de devolver un array:
```json
{
"rows": [ { "n": 0 }, { "n": 1 } ],
"truncated": true,
"row_limit": 1000,
"received_rows": 1001,
"message": "Resultado truncado a las primeras 1000 filas (límite MCP_MAX_ROWS)..."
}
```
Si el resultado cabe en el tope, la respuesta sigue siendo el array de filas de
siempre (no hay cambio de forma). Con `MCP_MAX_ROWS=0` el tope se desactiva.
- Las herramientas de metadatos (`list_databases`, `list_tables`, `get_table_schema`,
`get_table_schema_with_relations`) **fallan con un error claro si el tope recorta su
resultado**, en lugar de devolver un listado incompleto: es peor concluir que una tabla
no existe que recibir el aviso de subir `MCP_MAX_ROWS`.
- Los límites se reaplican tras cada cambio de base de datos, porque `COM_CHANGE_USER`
devuelve las variables de sesión a su valor por defecto.
## Requisitos
- **Node.js** >= 18 (recomendado 20 LTS)
- **MariaDB/MySQL** accesible desde la red del servidor
## Instalación
```bash
# Clonar o copiar la carpeta MariaDB-MCP
cd MariaDB-MCP
# Instalar dependencias
npm install
```
## Configuración
Copia el archivo de ejemplo y ajusta tus credenciales:
```bash
cp .env.example .env
```
Variables disponibles:
| Variable | Descripción | Default |
|----------|-------------|---------|
| `DB_HOST` | Host del servidor MariaDB | `localhost` |
| `DB_PORT` | Puerto | `3306` |
| `DB_USER` | Usuario | `root` |
| `DB_PASSWORD` | Contraseña | *(vacío)* |
| `DB_NAME` | Base de datos por defecto | `genoma` |
| `DB_CHARSET` | Charset de conexión | `utf8mb4` |
| `DB_SSL` | Habilitar SSL | `false` |
| `DB_SSL_CA` | Ruta al certificado CA | *(vacío)* |
| `DB_SSL_CERT` | Ruta al certificado cliente | *(vacío)* |
| `DB_SSL_KEY` | Ruta a la clave privada cliente | *(vacío)* |
| `DB_SSL_VERIFY_CERT` | Verificar certificado | `true` |
| `DB_SSL_VERIFY_IDENTITY` | Verificar identidad del host | `true` |
| `MCP_READ_ONLY` | Modo solo lectura | `true` |
| `MCP_MAX_POOL_SIZE` | Tamaño máximo del pool | `10` |
| `MCP_QUERY_TIMEOUT` | Segundos máximos por consulta (`0` = sin límite) | `30` |
| `MCP_MAX_ROWS` | Filas máximas devueltas por herramienta (`0` = sin límite) | `1000` |
| `MCP_API_KEY` | API key(s) exigida(s) en el transporte HTTP, separadas por coma | *(vacío = sin auth)* |
| `MCP_AUTH_REQUIRED` | `true` exige API key, `false` la desactiva | *exigir si hay clave* |
| `LOG_LEVEL` | Nivel de log (`debug`,`info`,`warn`,`error`) | `info` |
| `ALLOWED_ORIGINS` | Orígenes CORS separados por coma | *(vacío)* |
| `ALLOWED_HOSTS` | Hosts permitidos separados por coma | `localhost,127.0.0.1` |
## Uso
### Modo stdio (por defecto)
```bash
npm start
# o
node src/index.js
```
### Modo HTTP/SSE
```bash
node src/index.js --http 3000
```
### Ayuda
```bash
node src/index.js --help
```
### Autenticación API key (transporte HTTP)
Define una o varias claves en `.env` para proteger el endpoint MCP:
```bash
MCP_API_KEY=mi-clave-secreta
# o varias: MCP_API_KEY=clave-1,clave-2
```
Con la clave definida, **todas las rutas salvo `/health`** devuelven `401` si no
se envía. El cliente debe incluir una de estas cabeceras:
```http
Authorization: Bearer mi-clave-secreta
X-API-Key: mi-clave-secreta
```
```bash
# Verificar
$ curl -i -X POST http://localhost:3005/mcp -H 'Authorization: Bearer mi-clave-secreta'
HTTP/1.1 401 Unauthorized # sin clave
HTTP/1.1 200 OK # con clave correcta
$ curl -i http://localhost:3005/health # sigue sin clave para el healthcheck
# → {"status":"healthy"} (sin credenciales: solo el estado)
```
`/health` nunca exige credenciales, para no romper el healthcheck de Docker y de los
orquestadores. Cuando hay autenticación configurada solo responde `{ status }`; el
detalle (`server`, `version`, `database`, `readOnly`, `pool`) requiere la misma clave que
`/mcp`, igual que el mensaje de error cuando la base de datos no responde.
Variables relacionadas:
| Variable | Descripción |
|----------|-------------|
| `MCP_API_KEY` | Claves válidas separadas por coma. Vacío → autenticación deshabilitada (se avisa en el log). |
| `MCP_AUTH_REQUIRED` | `true` = exigir clave (el servidor **no arranca** si `MCP_API_KEY` está vacía). `false` = desactivar la autenticación aunque haya claves. Sin definir = exigir solo si hay claves. |
> La comparación de claves se hace en tiempo constante (hash SHA-256 + `timingSafeEqual`).
> La comparación no se registra en los logs.
## Docker
### Construir y ejecutar
```bash
# Modo stdio ( foreground )
docker-compose run --rm mcp-server
# Modo HTTP ( background )
docker-compose up -d mcp-server
```
> Asegúrese de que las variables de entorno estén definidas en un archivo `.env` o en el shell antes de ejecutar `docker-compose`.
## Seguridad
- **MULTI_STATEMENTS** y **LOCAL_INFILE** están desactivados en el pool de conexiones.
- En modo `READ_ONLY`, solo se permiten queries que inicien con `SELECT`, `SHOW`, `DESC`, `DESCRIBE` o `USE`.
- Se bloquean explícitamente las funciones `LOAD_FILE()` y sentencias `INTO OUTFILE/DUMPFILE`.
- Se detecta y se alerta si el usuario de base de datos posee el privilegio global `FILE`.
- **Límites de ejecución** por sesión (tiempo máximo de consulta y tope de filas), que
además acotan el consumo de memoria del servidor y el tamaño de las respuestas.
- **Autenticación API key** opcional en el transporte HTTP (`MCP_API_KEY`), con
comparación en tiempo constante; `/health` queda abierto para el healthcheck, pero
solo devuelve `{ status }` cuando hay autenticación configurada.
- **Solo lectura real con grants**: `MCP_READ_ONLY` filtra por prefijo de sentencia y es
una comodidad, no una frontera de seguridad (un CTE con `UPDATE`, una función
almacenada o una sentencia preparada se le escapan). La garantía la da el usuario de
base de datos, y al arrancar se avisa si ese usuario tiene privilegios de escritura.
Ejemplo listo para ejecutar en `examples/mariadb-readonly-user.sql`:
```sql
CREATE USER IF NOT EXISTS 'mcp_ro'@'%' IDENTIFIED BY '...';
GRANT SELECT, SHOW VIEW ON `genoma`.* TO 'mcp_ro'@'%';
```
y en `.env`: `DB_USER=mcp_ro` con su `DB_PASSWORD`.
- Cada ejecución verifica con `SELECT DATABASE()` que corre sobre la base de datos
solicitada; si no coincide, la conexión se descarta y se devuelve un error en vez
de resultados de otra base de datos.
## Estructura del proyecto
```
MariaDB-MCP/
├── src/
│ ├── index.js # Punto de entrada y CLI
│ ├── server.js # Lógica MCP, pool y herramientas
│ ├── config.js # Variables de entorno
│ └── logger.js # Configuración de winston
├── test/ # Pruebas (node --test)
├── examples/ # Ejemplos, p. ej. usuario MariaDB de solo lectura
├── logs/ # Archivos de log rotados
├── .env.example
├── .gitignore
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── package.json
└── README.md
```
## Licencia
MIT
TDQS
Scored across 6 tools
Each tool has a clear, non-overlapping purpose: database creation, SQL execution, schema retrieval (with optional relations), and listing databases/tables. The two schema tools are differentiated by the inclusion of foreign key relations.
All tool names follow a consistent verb_noun pattern in snake_case (e.g., create_database, list_databases, get_table_schema). The style is uniform and predictable.
Six tools cover essential database operations—listing, schema inspection, SQL execution, and database creation—without being excessive or sparse for the server's scope.
The set covers read operations well but lacks explicit tools for dropping databases or altering tables. The execute_sql tool can perform write operations if mode allows, but dedicated tools for update/delete are missing, leaving notable gaps.