Aller au contenu

Agents IA (MCP)

graal expose un serveur MCP (Model Context Protocol) : un agent IA s’y branche et pilote la plateforme avec les droits du compte qu’il porte, pas avec les siens. Le serveur ne parle pas à la base de données : il traduit MCP vers l’API REST de graal, en portant l’identité de l’appelant.

Une précision qui compte avant tout le reste : la plateforme et les données restent chez vous, mais ce que les outils renvoient à l’agent passe par le modèle de langage que vous avez choisi. Si ce modèle est hébergé par un tiers, la sortie de données est votre décision, pas un effet de bord. Voir Souveraineté et architecture.

Des outils de lecture, des outils d’écriture, et aucun outil destructif activé par défaut.

Outil Ce qu’il rend
list_projects Les projets du tenant
list_jobs Les jobs d’un projet ou du tenant (filtre par type, pagination)
get_job La définition complète d’un job
get_run L’état d’une exécution — sans identifiant, la plus récente du job
get_run_logs Le journal d’un run, suivi incrémental par curseur
list_catalog Le catalogue, couche par couche : couches → bases → tables → champs
list_bucket_files Les buckets, ou le contenu d’un chemin
get_costs Les coûts du tenant ou d’un projet sur une période
Outil Ce qu’il fait
upload_code Téléverse un fichier de code dans les librairies du tenant
create_job Crée un job dans un projet (Python, Spark, low-code ou bash)
run_job Lance une exécution, sans attendre son résultat
schedule_job Pose ou remplace la planification cron d’un job

C’est une décision d’architecture, pas un oubli, et elle est tenue à trois endroits du code plutôt que par une convention :

  • le type des opérations du serveur n’admet que GET, POST et PATCH — un appel DELETE ne compile pas ;
  • tous les outils sont annoncés au protocole avec destructiveHint: false ;
  • retirer une planification, supprimer un job ou un fichier reste un geste humain, dans la console.

Un agent peut donc créer, lancer et planifier du travail, et vous pouvez toujours revenir en arrière vous-même. Un agent qui se trompe encombre ; il ne détruit pas.

Mode Qui détient le secret D’où vient le tenant Pour quoi
Jeton présenté personne : l’agent apporte son jeton, le serveur le relaie du realm porté par le jeton l’usage normal, seul mode admis hors machine locale
Application graal le serveur, avec les identifiants d’une « Application graal » par tenant d’un en-tête ou de sa configuration poste local : démonstration, mise au point

Quatre garde-fous, dans cet ordre :

  1. Un jeton ne sort jamais de son tenant. L’émetteur (iss) du jeton doit valoir exactement <url Keycloak>/realms/<realm du tenant>, vérifié avant le premier appel à l’API.
  2. Le tenant n’est jamais un paramètre d’outil. Il vient de la session et part en en-tête ; un agent ne peut pas changer de tenant en changeant un argument.
  3. Le mode « Application graal » refuse de démarrer ailleurs que sur l’interface locale (127.0.0.1, ::1, localhost) : sur un port exposé, quiconque l’atteint obtiendrait les droits de l’agent. GRAAL_MCP_REQUIRE_BEARER=1 coupe entièrement ce mode.
  4. La frontière de sécurité est le RBAC par projet de l’API, pas le serveur MCP. Restreindre un agent à un projet par sa configuration (GRAAL_MCP_PROJECT_ID) est un confort ; les droits réels sont ceux du compte derrière le jeton.

Chaque écriture réussie est attribuée et notifiée :

  • le job créé porte les étiquettes graal.systems/created-by: agent et graal.systems/agent: agent-mcp ;
  • l’exécution lancée porte l’initiateur agent-mcp ;
  • une notification part vers le propriétaire du projet touché, plus les destinataires configurés. Une notification en échec n’annule pas l’action : le résultat rendu à l’agent dit explicitement si elle est passée.

Autrement dit, « qui a lancé ce run ? » a une réponse, portée par le run lui-même.

Le journal d’audit de la plateforme relie en plus chaque action à son acteur, humain ou agent (voir sécurité et gouvernance).

Le serveur écoute en Streamable HTTP sur 127.0.0.1:7332 par défaut : point d’entrée POST /mcp, vivacité GET /healthz (qui ne touche pas à l’API). Un transport stdio existe aussi, pour un agent lancé en sous-processus.

Côté client MCP, la configuration tient en quelques lignes :

{
"mcpServers": {
"graal": {
"type": "http",
"url": "http://127.0.0.1:7332/mcp",
"headers": { "X-Graal-Tenant": "mon-tenant" }
}
}
}

En mode « jeton présenté », ajouter un en-tête Authorization: Bearer <jeton> : le tenant est alors déduit du realm du jeton et l’en-tête X-Graal-Tenant devient inutile.

Pour lister les outils et l’opération REST derrière chacun sans rien démarrer : graal-mcp --tools.

Variable Rôle
GRAAL_API_URL Racine de l’API, préfixe /api/v1 compris
GRAAL_KEYCLOAK_URL Origine publique de Keycloak, qui sert à calculer l’émetteur attendu
GRAAL_MCP_TENANT, GRAAL_MCP_REALM Le tenant servi et son realm
GRAAL_MCP_CLIENT_ID, GRAAL_MCP_CLIENT_SECRET L’« Application graal » du tenant (mode Application)
GRAAL_MCP_TENANTS La même chose en JSON, pour servir plusieurs tenants
GRAAL_MCP_PROJECT_ID Restreint l’agent à un projet (confort, pas une isolation)
GRAAL_MCP_NOTIFY_TO Destinataires supplémentaires des notifications
GRAAL_MCP_REQUIRE_BEARER À 1, seul le mode « jeton présenté » est accepté
GRAAL_MCP_HOST, GRAAL_MCP_PORT Interface et port d’écoute (défaut 127.0.0.1:7332)

Aucune de ces variables ne contient un secret d’utilisateur : le serveur n’en stocke aucun, et il ne conserve un jeton en mémoire que le temps de sa validité.

Toutes les erreurs sont rendues sous la même forme, avec une catégorie stable que l’agent peut exploiter : feature_disabled, unauthorized, forbidden, not_found, api_error, auth_error, config_error, unknown_tenant, out_of_scope, precondition_failed, transport_error.

Le cas à connaître est le premier : une fonction désactivée sur l’installation répond 501 feature_disabled, jamais 404, et le message dit à l’agent de ne pas réessayer. Un 404 signifie donc vraiment « cette ressource n’existe pas », ce qui rend les deux cas distinguables sans deviner.

Le serveur parle MCP sur HTTP. Dans un client compatible (Claude Code, Claude Desktop, ou tout autre client MCP), la déclaration tient en quelques lignes :

{
"mcpServers": {
"graal": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "X-Graal-Tenant": "votre-locataire" }
}
}
}

Deux modes d’authentification, et le choix décide de qui agit :

Mode Comment Le locataire vient de Quand s’en servir
Compte de service Le serveur porte les identifiants d’une application, et le client nomme le locataire par X-Graal-Tenant l’en-tête Un agent qui travaille pour une équipe, sur un locataire donné
Jeton présenté Le client ajoute "Authorization": "Bearer <jeton>" dans headers le domaine du jeton Un agent qui agit pour le compte d’une personne, avec exactement ses droits

Une fois branché, l’agent dispose de tous les outils. Une demande comme « prépare un job qui agrège la consommation régionale et planifie-le tous les matins à 6 h » se traduit chez lui en :

list_projects → trouver le projet
upload_code → déposer le script dans le bucket du projet
create_job → créer le job (python, spark, bash ou lowcode — rien d'autre)
run_job → lancer une première exécution
get_run · get_run_logs → suivre, et lire les logs par curseur
schedule_job → poser le cron

Chaque écriture pousse une notification au propriétaire du projet et allume le badge « Agent » dans la console. Vous n’avez pas à demander à l’agent ce qu’il a fait : c’est écrit.

  • Aucune suppression. Aucun outil de suppression n’est activé, et un test refuse qu’on en ajoute un : aucun outil ne peut s’adosser à une opération DELETE ni s’appeler delete_*.
  • Un type de job connu. create_job n’accepte que python, spark, bash et lowcode, et refuse avant tout appel réseau.
  • Aucune valeur inventée. Si le type d’instance ou l’identité ne peut pas être déduit de ce que la plateforme connaît, l’outil s’arrête et nomme le geste humain attendu plutôt que de forcer l’écriture.