Aller au contenu
ENDemander une démo

Agents IA (MCP)

graal Data Platform 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 la Data Platform, en portant l’identité de l’appelant. C’est le même serveur pour tous les clients MCP compatibles, y compris les agents de graal Agentic Platform (voir Les deux produits).

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.

82 en lecture, 31 en écriture, dont 7 destructifs qui ne sont exposés que si l’exploitant le décide. Un serveur installé sans rien changer en expose 106, et aucun ne supprime quoi que ce soit ni n’arrête une exécution (stop_workspace met un espace en pause, fichiers gardés : il est réversible). Chaque outil s’adosse à une opération que l’API sert réellement : un test refuse qu’un outil tombe sur une fonction coupée.

Domaine Outils
Projets list_projects, get_project (et ses compteurs)
Jobs et exécutions list_jobs, get_job, list_runs, get_run, get_run_logs (par curseur), list_run_files, read_run_file
Workflows list_workflows, get_workflow, get_workflow_run, get_workflow_run_steps (chaque tentative de chaque étape)
Orchestration get_backfill (avancement d’un rejeu), list_triggers (les déclencheurs d’un projet, sans leur secret), list_trigger_events (ce qu’un déclencheur a lancé)
Buckets et fichiers list_bucket_files, read_bucket_file (texte en clair, binaire en base64, borné)
Catalogue list_catalog (couches → bases → tables → champs), get_table, search_catalog (les tables qu’un agent a le droit de lire)
Connexions de données list_connections (bases, S3, SFTP, API HTTP, HDFS d’un projet — le secret est désigné, jamais rendu), test_connection, inspect_connection (tables ou fichiers, échantillon borné)
Lignage et qualité list_lineage_datasets, get_lineage (le graphe autour d’un jeu, colonnes comprises), get_run_lineage, get_run_quality (le rapport des contrôles d’une exécution)
Entrepôts de données list_datawarehouses, get_datawarehouse — jamais leurs identifiants de connexion ; query_sql (une requête SQL de lecture, lignes bornées)
Low-code verify_lowcode (un DAG est-il valide ?), generate_lowcode_code (le code Pandas, PySpark… qu’il produit), list_templates (la galerie de gabarits), render_template (un gabarit rendu en DAG), verify_dag (un DAG typé est-il valide ?), get_preview (l’aperçu d’une étape)
Recherche search (projets, jobs, workflows, tables… filtrés par les droits de l’agent)
Espaces de travail list_workspaces, get_workspace (et ses derniers logs)
Expériences et modèles ML list_experiments, get_experiment (et les métriques de ses runs), list_experiment_runs, get_experiment_run, list_models
Serving de modèles list_endpoints, get_endpoint, predict (interroger un modèle servi ; rien n’est écrit)
Sessions interactives list_sessions (les noyaux Python ouverts et leur état)
Passerelle LLM et RAG llm_chat (une complétion au compte d’un projet, données personnelles masquées), list_llm_models, list_rag_indexes, search_documents, ask_documents (une réponse qui cite ses sources)
Assistants generate_pipeline (un pipeline low-code décrit en langage naturel, vérifié par le moteur), describe_table (une description de table proposée), get_drift (la dérive d’un modèle servi)
Agents gouvernés list_agents, list_agent_activity, get_approval_status (où en est une action soumise à validation), run_agent (un agent intégré : exploitation ou data engineer)
Bibliothèques et secrets list_libraries, list_secrets — le nom des secrets, jamais leur valeur
Infrastructures et runtimes list_infrastructures, list_runtimes, list_instance_types
Quotas, usages, coûts list_quotas, list_usages, get_costs, get_costs_by_project, get_usage_metrics (CPU et mémoire réellement consommés)
Activité list_activity (connexions, notifications, fil d’activité d’un projet), list_audit_actions (le journal des actions, humaines ou d’agent), export_audit_actions (toute une période)
Identité list_identity_providers (configuration seulement, jamais un secret), get_mfa_policy
Facturation list_invoices, get_invoice (et son PDF, borné), list_transactions, get_transaction, list_payment_methods (IBAN masqué), get_payment_provider — lecture seule

verify_lowcode, generate_lowcode_code, render_template et verify_dag envoient un DAG ou un gabarit au moteur low-code, qui valide, traduit ou rend sans rien enregistrer : ce sont des lectures. test_connection et inspect_connection aussi : la plateforme ouvre la connexion, liste ou lit un échantillon, et n’écrit rien. list_audit_actions exige le droit de lecture du journal d’audit : un agent qui ne l’a pas reçoit un refus, pas une liste vide.

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
rerun_run Relance une exécution passée, mêmes paramètres, même infrastructure
run_workflow Lance un workflow
upload_bucket_file Dépose un fichier dans un bucket
set_table_description Documente une table du catalogue
create_workspace Démarre un espace de travail Jupyter ou VS Code
start_workspace Redémarre un espace arrêté, fichiers retrouvés tels quels
stop_workspace Arrête un espace pour libérer ses ressources, fichiers gardés
comment_activity Commente une activité du fil d’un projet, signé de l’identité de l’agent
follow_resource Abonne l’identité de l’agent à une ressource (projet, job, workflow, table…)
create_session Ouvre une session Python interactive (un noyau, dans son espace de travail)
run_session_cell Exécute une cellule de code dans le noyau d’une session et rend ses sorties
register_model_version Enregistre une version de modèle depuis une exécution du projet
set_model_version_stage Passe une version de modèle d’étape (Staging, Production…) ou d’alias
schedule_workflow Pose ou remplace la planification cron d’un workflow (fuseau, rattrapage des échéances manquées)
create_backfill Rejoue la planification d’un job ou d’un workflow sur une période passée (1 à 5 exécutions à la fois)
create_trigger Crée un déclencheur : fin d’une exécution, fichier déposé dans un bucket, ou webhook signé
create_endpoint Déploie une version de modèle du registre derrière un endpoint
scale_endpoint Change le nombre de répliques d’un endpoint (0 l’arrête, réversible)
create_connection Déclare une connexion de données d’un projet ; son secret est désigné, jamais transmis
preview_lowcode_step Lance l’aperçu d’une étape d’un pipeline low-code (une exécution éphémère)

Destructifs, fermés par défaut (GRAAL_MCP_ALLOW_DESTRUCTIVE=1 pour les ouvrir) :

Outil Ce qu’il fait
cancel_run Arrête une exécution de job en cours
cancel_workflow_run Arrête une exécution de workflow en cours
delete_workspace Supprime un espace de travail (fichiers internes perdus, buckets et tables intacts)
delete_bucket_file Supprime jusqu’à 20 fichiers d’un bucket (jamais un répertoire)
delete_session Supprime une session interactive et son noyau
cancel_backfill Arrête un rejeu en cours
delete_trigger Supprime un déclencheur

Ce qu’un agent peut détruire, et qui le décide

Section intitulée « Ce qu’un agent peut détruire, et qui le décide »

Par défaut, rien. C’est une décision d’architecture, tenue dans le code plutôt que par une convention :

  • les sept outils qui annulent ou suppriment ne sont enregistrés que si l’exploitant du serveur pose GRAAL_MCP_ALLOW_DESTRUCTIVE=1 ; sans cela, l’agent ne les voit même pas ;
  • un test refuse qu’un autre outil appelle une opération DELETE ;
  • ouverts, ils sont annoncés au protocole avec destructiveHint: true, pour que le client MCP demande confirmation, et ils passent par la même garde que toute écriture : droits du projet, notification au propriétaire ;
  • supprimer un projet, un job, un workflow, un secret ou une table, et retirer une planification, restent des gestes humains, dans la console. Aucun outil ne le fait, option ou non.

Un agent qui se trompe encombre ; il ne détruit que ce que vous lui avez explicitement permis de détruire.

list_secrets rend des noms, pour qu’un job puisse les référencer ; aucun outil ne rend la valeur d’un secret, un jeton, un mot de passe d’entrepôt, un kubeconfig ni une clé S3. Dans l’autre sens, aucun outil n’en reçoit : une connexion de données désigne son secret par son identifiant, et un mot de passe passé en argument est refusé avant tout appel. Chaque sortie est réduite aux champs utiles, puis repassée par un filtre qui masque toute valeur rangée sous un nom de secret.

Une seule exception, voulue et testée : create_trigger rend une fois le secret de signature d’un webhook qu’il vient de créer, avec l’adresse du webhook et la règle de signature (HMAC-SHA256 de l’horodatage et du corps). La plateforme ne le rend qu’à la création ; aucun autre outil ne le laisse sortir, et un secret perdu se renouvelle dans la console.

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_ALLOW_DESTRUCTIVE À 1, enregistre les 7 outils destructifs (fermés par défaut)
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. Un serveur exposé au-delà de la machine locale exige le jeton de l’appelant. 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": { "Authorization": "Bearer <jeton>" }
}
}
}

Le jeton décide de qui agit :

  • le jeton d’une application, c’est-à-dire d’un compte de service (voir rôles et permissions) : l’agent travaille pour une équipe, avec les rôles que cette application détient sur ses projets ;
  • le jeton d’une personne : l’agent agit pour le compte de cette personne, avec exactement ses droits.

Dans les deux cas, le locataire vient du domaine du jeton.

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.

  • Rien de destructif par défaut. Les 7 outils qui annulent ou suppriment ne sont enregistrés qu’avec GRAAL_MCP_ALLOW_DESTRUCTIVE=1, et un test refuse qu’un autre outil s’adosse à une opération 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.