Brancher une base de données
Donner un accès base à un agent : utilisateur en lecture seule, vues plutôt que tables, identifiants hors du prompt.
Donner l'accès à la base à un agent change tout : plus besoin d'exporter un CSV pour savoir combien de clients n'ont rien commandé depuis 90 jours, la question se pose en français et la réponse arrive. C'est probablement le branchement qui fait gagner le plus de temps.
C'est aussi celui où l'on se fait le plus peur. Un agent qui écrit du SQL peut
écrire DELETE. Il peut aussi écrire un SELECT parfaitement correct qui
ramène huit millions de lignes et met le serveur à genoux, ou qui remonte la
table des utilisateurs avec les hachages de mots de passe dans le contexte du
modèle.
La bonne nouvelle : aucun de ces trois risques ne se traite au niveau du prompt. Ils se traitent au niveau de la base, avec des mécanismes qui existent depuis trente ans. Un utilisateur en lecture seule, des requêtes bornées, des identifiants qui ne transitent jamais par une conversation.
La règle de base
Ce n'est pas le modèle qui doit s'interdire d'écrire, c'est la base qui doit
l'en empêcher. Une consigne dans un prompt est une préférence, un GRANT est
une contrainte. Tout ce qui suit découle de cette distinction.
Ne jamais brancher sur la base de production en premier
Commence sur un réplica de lecture, une copie de recette, ou un dump anonymisé. Tu découvriras en une semaine quelles tables l'agent utilise vraiment, et l'accès en production, si tu le donnes, sera beaucoup plus étroit.
Avant de commencer
- Une base PostgreSQL et le droit d'y créer un rôle
- Node.js, pour lancer le serveur MCP via
npx - Un gestionnaire de secrets, même minimal
1Créer un utilisateur qui ne peut que lire
Trois couches : les droits, le mode lecture seule par défaut, et les délais maximum.
CREATE ROLE agent_ro LOGIN PASSWORD 'mot-de-passe-genere';
GRANT CONNECT ON DATABASE analytics TO agent_ro;
GRANT USAGE ON SCHEMA public TO agent_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro;
-- Les tables creees plus tard seront lisibles aussi
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO agent_ro;
Puis la ceinture, en plus des bretelles :
ALTER ROLE agent_ro SET default_transaction_read_only = on;
ALTER ROLE agent_ro SET statement_timeout = '15s';
ALTER ROLE agent_ro SET idle_in_transaction_session_timeout = '30s';
default_transaction_read_only place chaque transaction de ce rôle en lecture
seule ; une écriture échoue même si un droit avait été accordé par erreur.
statement_timeout interrompt toute requête qui dépasse le délai : c'est la
protection contre la jointure oubliée, pas contre la malveillance. Les deux
délais s'expriment en millisecondes par défaut, mais acceptent une unité
explicite comme ici.
Vérifier plutôt que croire
Connecte-toi avec ce rôle et lance un DELETE sur une table de test. Tu dois
obtenir une erreur. Tant que tu n'as pas vu l'erreur de tes yeux, la
configuration n'est pas en place.
2Ne pas exposer les tables, exposer des vues
Un GRANT SELECT sur toute la base laisse passer les colonnes que tu ne veux
pas voir arriver dans le contexte d'un modèle : mots de passe, jetons, e-mails,
données de santé, coordonnées bancaires.
La réponse est classique : des vues qui ne contiennent que les colonnes utiles, et le rôle n'a de droits que sur les vues.
CREATE VIEW v_clients_agent AS
SELECT id, ville, code_postal, segment, date_creation, statut
FROM clients;
REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM agent_ro;
GRANT SELECT ON v_clients_agent, v_commandes_agent TO agent_ro;
Bénéfice secondaire : le modèle voit un schéma plus petit, donc il se trompe moins souvent de table et consomme moins de contexte pour le comprendre.
3Brancher le serveur MCP
La documentation de Claude Code donne l'exemple avec DBHub, le paquet
@bytebase/dbhub, qui prend une chaîne de connexion en --dsn et couvre
PostgreSQL, MySQL, MariaDB, SQL Server et SQLite :
claude mcp add --transport stdio db -- npx -y @bytebase/dbhub \
--dsn "postgresql://agent_ro:MOT_DE_PASSE@replica.interne:5432/analytics"
Puis :
claude mcp list
Le serveur doit apparaître comme connecté.
Un point que je n'ai pas pu vérifier
DBHub annonce un mode lecture seule et une limitation du nombre de lignes, mais je n'ai pas trouvé les noms exacts des options correspondantes dans sa documentation en ligne des options de ligne de commande. Vérifie-les dans sa documentation avant de compter dessus, et considère-les de toute façon comme un confort : la garantie reste l'utilisateur en lecture seule.
4Sortir les identifiants de la conversation
Le mot de passe ne doit jamais apparaître dans un message, un fichier de projet commité, ni un prompt système. Trois règles.
Les variables d'environnement, pas le texte. Claude Code accepte de les passer à l'ajout du serveur :
claude mcp add --env DSN="postgresql://agent_ro:...@replica:5432/analytics" \
--transport stdio db -- npx -y @bytebase/dbhub
Le fichier partagé référence, il ne contient pas. Dans un .mcp.json
commité, écris ${DSN} plutôt que la valeur. Chacun fournit la sienne, et le
fichier reste versionnable. Si la variable n'est pas définie, Claude Code
signale un avertissement dans claude mcp list au lieu de démarrer avec une
valeur fausse.
Un mot de passe par usage, révocable. Le rôle agent_ro a son propre mot de
passe, distinct de tout le reste. Le jour où tu veux couper l'accès, un
ALTER ROLE agent_ro NOLOGIN suffit, sans toucher à rien d'autre.
5Borner les requêtes dans le prompt aussi
Les garde-fous techniques empêchent le désastre. Le prompt, lui, améliore la qualité des réponses.
Regles pour interroger la base :
1. Toute requete exploratoire se termine par LIMIT 100.
2. Avant de compter ou d'agreger, montre-moi la requete et attends mon
accord si elle touche plus de 3 tables.
3. Ne renvoie jamais de colonne contenant un e-mail, un nom, un
telephone ou un identifiant technique dans tes resultats : agrege ou
anonymise.
4. Si une question demande une donnee absente du schema, dis-le au lieu
d'approcher avec une autre colonne.
5. Montre toujours le SQL execute a cote du resultat.
Le point 5 est le plus utile au quotidien : il te permet de repérer en un coup d'œil une jointure qui duplique des lignes et gonfle un total.
Le récapitulatif
| Risque | Ce qui le traite réellement |
|---|---|
| Écriture ou suppression | Rôle sans droits d'écriture, plus default_transaction_read_only |
| Requête qui sature le serveur | statement_timeout, et un réplica plutôt que la production |
| Colonnes sensibles dans le contexte | Des vues, pas des tables |
| Identifiants qui fuitent | Variables d'environnement, ${VAR} dans le fichier partagé |
| Totaux faux | Le SQL affiché à côté du résultat |
| Accès à couper vite | Un rôle dédié, ALTER ROLE ... NOLOGIN |
Tu as d’autres ressources comme ça ?
Ce que tu viens de lire est entièrement gratuit. Une nouvelle ressource part chaque semaine dans la newsletter : guides, modèles à copier et liens triés, sans compte à créer.
Me contacter
Une question, un projet, une envie de collaborer ? Écris-moi.
Cette page est en accès libre, n’hésite pas à la partager.