DonnIA
Toutes les ressources
GuideData & RAG17 min

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

RisqueCe qui le traite réellement
Écriture ou suppressionRôle sans droits d'écriture, plus default_transaction_read_only
Requête qui sature le serveurstatement_timeout, et un réplica plutôt que la production
Colonnes sensibles dans le contexteDes vues, pas des tables
Identifiants qui fuitentVariables d'environnement, ${VAR} dans le fichier partagé
Totaux fauxLe SQL affiché à côté du résultat
Accès à couper viteUn 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.

Cette page est en accès libre, n’hésite pas à la partager.