A
Asterdb MCP
by @PmaControl
Un serveur mcp pour MariaDB/MySQL en readonly
Created 3/7/2026
Updated about 4 hours ago
README
Repository documentation and setup instructions
MCP MariaDB/MySQL Server (PHP)
Serveur MCP (Model Context Protocol) en PHP pour MariaDB/MySQL, orienté lecture.
English version: README_en.md
Auteur
- Aurélien LEQUOY
Licence
Ce projet est distribué sous licence GNU GPL v3.
- Licence: GPL-3.0-or-later
- Texte officiel: https://www.gnu.org/licenses/gpl-3.0.html
Fonctionnalités
- Endpoint santé:
GET /health - Endpoint MCP JSON-RPC:
POST /mcp - Transport compatible Streamable HTTP
- Authentification Bearer optionnelle via
MCP_TOKEN - Outils SQL:
db_selectdb_tablesdb_schemadb_indexesdb_explaindb_explain_tabledb_processlistdb_variables
Architecture
Structure “1 fichier = 1 classe”:
public/index.php(point d’entrée web)src/Env.phpsrc/Http.phpsrc/Db.phpsrc/SqlGuard.phpsrc/JsonRpc.phpsrc/Tools.phpsrc/App.php
Prérequis
- Debian/Ubuntu (recommandé)
- Apache 2.4+
- PHP 8.2+
- Extensions PHP:
pdopdo_mysqlmbstring(recommandé)
- Accès réseau à la base MariaDB/MySQL
Installation complète (Apache)
Installation rapide (root) sur Ubuntu 24.04 / Debian 12 / Debian 13:
chmod +x install.sh
./install.sh
1. Déployer le code
cd /srv/www
git clone https://github.com/PmaControl/AsterDB-MCP.git mcp-mariadb
cd /srv/www/mcp-mariadb
2. Configurer l’environnement
Créer/éditer .env:
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=pmacontrol
DB_USER=mcp_ro
DB_PASS=change_me
MCP_TOKEN=change_me_if_needed
MAX_ROWS_DEFAULT=1000
MAX_ROWS_HARD=5000
MAX_SELECT_TIME_MS=30000
WHERE_FULLSCAN_MAX_ROWS=30000
MCP_QUERY_LOG=/srv/www/mcp-mariadb/mcp_mariadb_query.log
Notes:
MCP_TOKENvide (MCP_TOKEN=) => pas d’authMCP_TOKENnon vide => headerAuthorization: Bearer <token>obligatoireMAX_ROWS_DEFAULT=1000applique une limite par défaut de 1000 lignesMAX_ROWS_HARD=5000impose une limite maximum absolue de 5000 lignesMAX_SELECT_TIME_MSlimite la durée max des requêtesSELECT- MariaDB (>= 10.1.1): via
SET STATEMENT max_statement_time=... FOR SELECT ... - MySQL (>= 5.7.4): via hint
/*+ MAX_EXECUTION_TIME(...) */
- MariaDB (>= 10.1.1): via
- Valeur recommandée:
30000(30s). Ce seuil laisse passer des requêtes analytiques plus lourdes tout en gardant une limite de protection. WHERE_FULLSCAN_MAX_ROWS=30000fixe le seuil de refus pour les full scans avecWHERE.MCP_QUERY_LOGdéfinit le fichier de log JSONL des requêtes MCP SQL (SQL formatée,rowCount,durationMs,plan).
Exemple MySQL:
SELECT /*+ MAX_EXECUTION_TIME(30000) */ *
FROM huge_table;
Exemple MariaDB:
SET STATEMENT max_statement_time=30 FOR
SELECT *
FROM huge_table;
Créer l'utilisateur MySQL/MariaDB (exemple compatible):
CREATE USER IF NOT EXISTS `cline`@`%` IDENTIFIED BY 'change_me';
GRANT SELECT ON *.* TO `cline`@`%`;
FLUSH PRIVILEGES;
3. Permissions
chown -R www-data:www-data /srv/www/mcp-mariadb
find /srv/www/mcp-mariadb -type d -exec chmod 755 {} \;
find /srv/www/mcp-mariadb -type f -exec chmod 644 {} \;
4. Activer les modules Apache nécessaires
a2enmod rewrite headers setenvif
5. Créer le VirtualHost Apache
Créer /etc/apache2/sites-available/mcp-mariadb.conf:
<VirtualHost *:13306>
ServerName localhost
DocumentRoot /srv/www/mcp-mariadb/public
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
<Directory /srv/www/mcp-mariadb/public>
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
<Location "^/(mcp|health)$">
Require local
Require ip 10.68.68.0/24
</Location>
ErrorLog ${APACHE_LOG_DIR}/mcp_mariadb_error.log
CustomLog ${APACHE_LOG_DIR}/mcp_mariadb_access.log combined
</VirtualHost>
Adapter:
ServerName- la règle réseau
Require ip ... Require ip 10.68.68.0/24signifie: seules les IP de10.68.68.1à10.68.68.254(CIDR/24) peuvent accéder à/mcpet/health, en plus deRequire local(localhost).
6. Activer le site et redémarrer Apache
a2ensite mcp-mariadb.conf
a2dissite 000-default.conf
systemctl reload apache2
# ou
service apache2 restart
7. Vérification Apache
apache2ctl configtest
systemctl status apache2
Tests de fonctionnement
Healthcheck
curl -sS http://<HOST>:13306/health
Initialize MCP (avec token)
curl -sS -X POST http://<HOST>:13306/mcp \
-H 'content-type: application/json' \
-H 'authorization: Bearer <MCP_TOKEN>' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
Initialize MCP (sans token)
curl -sS -X POST http://<HOST>:13306/mcp \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
Ping MCP
curl -sS -X POST http://<HOST>:13306/mcp \
-H 'content-type: application/json' \
-H 'authorization: Bearer <MCP_TOKEN>' \
--data '{"jsonrpc":"2.0","id":2,"method":"ping","params":{}}'
Tool db_explain_table (EXPLAIN lisible)
curl -sS -X POST http://<HOST>:13306/mcp \
-H 'content-type: application/json' \
-H 'authorization: Bearer <MCP_TOKEN>' \
--data '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"db_explain_table","arguments":{"sql":"SELECT id,id_mysql_server,port FROM alias_dns WHERE id_mysql_server = 113 ORDER BY id DESC LIMIT 50"}}}'
Configuration MCP Inspector (Streamable HTTP)
- Transport: Streamable HTTP
- URL:
http://<HOST>:13306/mcp - Authentication:
None - Si
MCP_TOKENest défini, ajouter le header:Authorization: Bearer <MCP_TOKEN>
Sécurité
- Utiliser un compte DB à privilèges minimum (lecture seule recommandé)
- Donner uniquement les droits nécessaires (
SELECTrecommandé) - Restreindre l’accès réseau Apache (
Require ip) - Utiliser un token fort pour
MCP_TOKEN - Mettre le service derrière HTTPS (reverse proxy/Nginx/Apache TLS)
- Les requêtes
SELECT ... FOR UPDATEsont bloquées explicitement db_selectapplique une politique de requête:SELECT *sansWHEREest autorisé uniquement sur une seule table sansJOINSELECT *avecWHEREbloqué uniquement si la table ciblée a plus de 30 colonnes- CTE non récursifs (
WITH ...) autorisés - CTE récursifs (
WITH RECURSIVE ...) bloqués ORdansWHEREbloqué (réécrire avecUNION/UNION ALL)- avec
WHERE, un full scan est autorisé si la table a au plus30000lignes - avec
WHERE, un full scan est refusé si la table dépasse30000lignes - garde charge DB: si plus de
3requêtes SQL sont déjà en cours,db_selectrenvoiedatabase busy retry in 1 second - si la requête dépasse le timeout SQL, l'erreur renvoyée est normalisée en:
guard [execution time reached]
Dépannage
404sur/mcpaveccurl: vérifier que vous faites un POST (pas GET)Unauthorized: token manquant ou invalide- Erreurs CORS Inspector: vérifier
OPTIONS /mcp(204) et headers CORS - Vérifier logs:
- Apache access:
/var/log/apache2/mcp_mariadb_access.log - Apache error:
/var/log/apache2/mcp_mariadb_error.log - SQL MCP (JSONL):
/srv/www/mcp-mariadb/mcp_mariadb_query.log
- Apache access:
Tests PHPUnit
- Installation système recommandée:
apt-get install -y phpunit - Lancer les tests:
phpunit --configuration phpunit.xml
- Suite actuelle:
tests/ToolsDbSelectPolicyTest.php- cas mockés sur
EXPLAIN, estimation de lignes (TABLE_ROWS) et validation des règlesdb_select
- cas mockés sur
- Rejeu des requêtes complexes:
tests/ToolsComplexQueriesReplayTest.php - Génération d'un rapport Markdown des requêtes replay (source, SQL formatée, explain, temps, succès/erreur, rows):
php scripts/generate_mcp_test_report.php
- Fichier généré:
docs/mcp_test_queries_report.md - Génération du catalogue MYXPLAIN (
explain_*.json) avec ~100 requêtesdb_explain(règleid_xxx -> xxx.id):
php scripts/generate_myxplain_query_catalog.php
- Fichiers générés:
docs/myxplain_query_catalog.mddocs/myxplain_query_catalog.json
- Le catalogue exécute chaque cas via
db_explain_tableet stocke:- exécution réelle
db_select(vraiReturned rows+ vraiExecution time (ms)) db_explain_table(tableau EXPLAIN lisible)- statut pass/fail et raison d'échec pour les 2 exécutions
- vérification
Expected signaturevsSignature match - filtre des relations: tables avec
TABLE_ROWS >= 100
- exécution réelle
- Source des cas:
https://github.com/cpeintre/MYXPLAIN/tree/master/data
Docker
Build local:
docker build -t asterdb-mcp:local .
Run local:
docker run --rm -p 13307:13306 \
-e DB_HOST=10.68.68.111 \
-e DB_PORT=3306 \
-e DB_NAME=pmacontrol \
-e DB_USER=cline \
-e DB_PASS=cline \
-e MCP_TOKEN=change_me_if_needed \
asterdb-mcp:local
CI/CD GitHub + GHCR + Docker Hub
- CI:
.github/workflows/ci.yml- déclenchement:
pushsurmain+pull_request - exécute
phpunit --configuration phpunit.xml
- déclenchement:
- CD:
.github/workflows/cd-ghcr.yml- déclenchement:
pushsurmainet tagsv* - build multi-arch (
linux/amd64,linux/arm64) - push vers
ghcr.io/pmacontrol/asterdb-mcp - push vers
docker.io/pmacontrol/asterdb-mcp - utilise
GITHUB_TOKEN(permissionspackages: write) - utilise aussi les secrets GitHub:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN
- déclenchement:
Authentification GHCR en local (PAT classic):
export CR_PAT=YOUR_TOKEN
echo \"$CR_PAT\" | docker login ghcr.io -u <github_username> --password-stdin
Pull image:
docker pull ghcr.io/pmacontrol/asterdb-mcp:latest
Run image GHCR:
docker run --rm -p 13307:13306 ghcr.io/pmacontrol/asterdb-mcp:latest
Run image GHCR avec configuration BDD:
docker run --rm -p 13307:13306 \
-e DB_HOST=IP_OU_HOST_DB \
-e DB_PORT=3306 \
-e DB_NAME=sakila \
-e DB_USER=cline \
-e DB_PASS=change_me \
-e MCP_TOKEN=change_me_if_needed \
ghcr.io/pmacontrol/asterdb-mcp:latest
Pull image Docker Hub:
docker pull pmacontrol/asterdb-mcp:latest
Run image Docker Hub:
docker run --rm -p 13307:13306 pmacontrol/asterdb-mcp:latest
Commandes utiles
# Redémarrer Apache
service apache2 restart
# Voir les logs en direct
tail -f /var/log/apache2/mcp_mariadb_access.log /var/log/apache2/mcp_mariadb_error.log /srv/www/mcp-mariadb/mcp_mariadb_query.log
Quick Setup
Installation guide for this server
Installation Command (package not published)
git clone https://github.com/PmaControl/AsterDB-MCP
Manual Installation: Please check the README for detailed setup instructions and any additional dependencies required.
Cursor configuration (mcp.json)
{
"mcpServers": {
"pmacontrol-asterdb-mcp": {
"command": "git",
"args": [
"clone",
"https://github.com/PmaControl/AsterDB-MCP"
]
}
}
}