Le cœur open source
Le cœur de akko-mcp-trino, sous licence Apache 2.0, donne à un agent IA un accès MCP en lecture seule à Trino, sous une identité configurée ou sous celle de chaque utilisateur.
Le cœur de akko-mcp-trino est un serveur MCP pour Trino, sous licence Apache 2.0, en version 0.5.3. Il donne à un agent IA six outils pour explorer un Trino et y lire des données, avec une garde de lecture seule décidée sur l’arbre SQL, des bornes à chaque appel et une ligne d’audit par appel. Chaque requête s’exécute soit sous l’identité que l’administrateur configure, soit sous celle de l’utilisateur pour qui l’agent travaille, dont Trino vérifie lui-même le jeton. Dans les deux cas, Trino et son moteur de politique décident de ce que l’agent peut lire.
Le cœur, et ce que livrent AKKO et ARCHE
| Cœur open source | Dans AKKO et ARCHE | |
|---|---|---|
| Identité dans Trino | une identité configurée par l’administrateur, ou celle de chaque utilisateur quand Trino fait confiance au même fournisseur d’identité | les mêmes, et d’autres façons de porter l’identité de l’utilisateur |
| Lecture seule sur l’arbre SQL | oui | oui |
| Bornes de lignes et de temps | oui | oui |
| Traces | une ligne d’audit par appel, avec l’appelant | un audit relié au journal du moteur |
| Outils | six outils d’exploration et de lecture | les mêmes, et des outils supplémentaires |
| Contexte métier du catalogue | non | oui, sans jamais décrire ce que la personne ne voit pas |
| Gestion des agents et des accès | non | oui |
| Exploitation en entreprise | à votre charge | installée et suivie avec la plateforme |
Le cœur convient à un agent qui lit sous un compte dont vos règles bornent les droits, comme à des agents qui lisent chacun sous les droits de la personne, sur un Trino qui vérifie les jetons de votre fournisseur d’identité. La présentation décrit le serveur complet.
Installer
pip install akko-mcp-trino # ou : pipx install akko-mcp-trino
uvx akko-mcp-trino --version # sans installation, comme le font la plupart des hôtes MCP
Vous pouvez aussi l’installer depuis la source.
git clone https://github.com/AKKO-p/akko-mcp-trino.git
cd akko-mcp-trino
python -m venv .venv && source .venv/bin/activate
pip install -e .
Il faut Python 3.12 ou plus récent. Le paquet installe un module Python (akko_mcp_trino) et une commande (akko-mcp-trino) ; python -m akko_mcp_trino lance le même serveur.
En conteneur
L’image est publiée sur Docker Hub, pour linux/amd64 et linux/arm64. Elle tourne sous un utilisateur non root et porte une sonde de santé. Elle se configure avec les mêmes variables d’environnement, sur les ports 3000 (MCP) et 3001 (santé).
docker run --rm akkoai/akko-mcp-trino:0.5 akko-mcp-trino --version
docker run -d --name akko-mcp-trino -p 3000:3000 -p 3001:3001 \
-e TRINO_HOST=trino.example.internal -e TRINO_PORT=8443 -e TRINO_HTTP_SCHEME=https \
-e TRINO_CATALOG=iceberg -e TRINO_IDENTITY_MODE=jwt \
-e MCP_AUTH_ENABLED=true -e MCP_AUTH_REQUIRED=true \
-e MCP_JWKS_URL=https://idp.example.com/realms/data/protocol/openid-connect/certs \
-e MCP_OIDC_ISSUER=https://idp.example.com/realms/data \
-e MCP_OIDC_AUDIENCE=data-platform \
akkoai/akko-mcp-trino:0.5
L’étiquette 0.5 suit la dernière version de la branche 0.5 ; 0.5.3 désigne une version précise.
Le code, les tests et les exemples sont sur GitHub ; le journal des versions suit chaque changement, et une vulnérabilité se signale selon la politique de sécurité.
Démarrer
Le serveur lit toute sa configuration dans l’environnement. Pointez-le sur votre Trino et sur votre fournisseur d’identité, puis lancez-le. Cet exemple utilise le mode par défaut, service, où chaque requête s’exécute sous TRINO_USER.
export TRINO_HOST=trino.example.internal
export TRINO_PORT=8443
export TRINO_HTTP_SCHEME=https
export TRINO_CATALOG=iceberg
export TRINO_USER=agent_lecture # l'identité sous laquelle le serveur lit
export TRINO_PASSWORD=... # authentification Basic, https seulement
export MCP_AUTH_ENABLED=true
export MCP_AUTH_REQUIRED=true # refuse une requête sans identité vérifiée
export MCP_JWKS_URL=https://idp.example.com/realms/data/protocol/openid-connect/certs
export MCP_OIDC_ISSUER=https://idp.example.com/realms/data
export MCP_OIDC_AUDIENCE=data-platform
akko-mcp-trino --check # la configuration effective, sans aucun secret
akko-mcp-trino # sert en streamable-http sur le port 3000
--check affiche la configuration effective sans jamais imprimer un secret, et sort avec le code 2 si le serveur refuserait de démarrer. Au démarrage, le serveur écrit une ligne comme celle-ci.
INFO:__main__:serving transport=streamable-http port=3000 auth=True strict=True extensions=none
Trois points permettent ensuite de suivre le serveur, sur le port de santé.
curl -s localhost:3001/health # {"status":"ok"} ne touche jamais Trino
curl -s localhost:3001/ready # {"status":"ready"} Trino répond
curl -s localhost:3001/metrics # métriques Prometheus
/health ne touche jamais Trino, pour qu’une sonde de vie ne tue pas le serveur parce que Trino est lent. /ready interroge Trino selon le mode d’identité : un SELECT 1 borné sous TRINO_USER en mode service, le point public /v1/info de Trino en mode jwt. /metrics expose mcp_trino_queries_total, mcp_trino_query_errors_total et mcp_trino_query_duration_seconds.
L’identité dans Trino
Le serveur vérifie d’abord le jeton de l’appelant (signature contre le JWKS du fournisseur, émetteur, audience, expiration). En mode strict (MCP_AUTH_REQUIRED=true), une requête sans jeton vérifié est refusée avant toute requête. La variable TRINO_IDENTITY_MODE dit ensuite sous quelle identité Trino exécute.
| Mode | Qui Trino voit | Ce que Trino doit avoir | Qui décide |
|---|---|---|---|
service (défaut) |
TRINO_USER, le même pour tous les appelants |
un mot de passe, ou rien pour un Trino local | les droits de cette seule identité |
jwt |
l’appelant, par son propre jeton vérifié | son authentification JWT, qui fait confiance au même fournisseur d’identité | les droits de chaque utilisateur |
Une identité configurée (service)
Tous les appels de tous les agents qui utilisent ce serveur lisent avec les mêmes droits. Donnez à ce compte un périmètre étroit dans votre moteur de politique (OPA, Ranger ou le contrôle d’accès de Trino) : les catalogues, les schémas et les colonnes dont tous les appelants ont besoin, et rien de plus. Le jeton de l’appelant ne quitte jamais le serveur. Une identité envoyée par l’appelant, dans un en-tête par exemple, ne change rien.
Chaque utilisateur sous ses propres droits (jwt)
Quand Trino fait confiance au même fournisseur d’identité, laissez-le décider pour chaque personne.
export TRINO_IDENTITY_MODE=jwt # le jeton vérifié de l'appelant part vers Trino
export TRINO_HTTP_SCHEME=https # obligatoire : un jeton ne circule jamais en http
export MCP_AUTH_ENABLED=true
export MCP_AUTH_REQUIRED=true
# TRINO_USER et TRINO_PASSWORD ne servent pas
Côté Trino, réglez http-server.authentication.type=JWT, http-server.authentication.jwt.key-file sur l’adresse du JWKS du fournisseur, et http-server.authentication.jwt.principal-field sur la même revendication que le serveur lit (preferred_username ou sub). required-issuer et required-audience attachent le jeton à votre fournisseur et à Trino ; l’audience du jeton doit être une audience que Trino accepte.
Le serveur transmet à Trino le jeton qu’il a vérifié, en https seulement, et jamais dans la ligne d’audit. Trino vérifie ce jeton à son tour et exécute la requête sous le nom de la personne : Ranger ou OPA décident pour elle, sans compte de service, sans mot de passe et sans droit d’agir pour quelqu’un d’autre. Une requête sans jeton vérifié est refusée ; elle n’est jamais exécutée sous TRINO_USER. Le serveur refuse de démarrer en mode jwt sur http, et un mode inconnu l’empêche de démarrer.
Ce mode a été prouvé sur un cluster réel : deux comptes posent la même question et obtiennent deux réponses, les emails masqués pour l’un et en clair pour l’autre ; une requête sans jeton et une requête avec un jeton falsifié sont refusées.
Les six outils
| Outil | Arguments | Ce qu’il rend |
|---|---|---|
list_catalogs |
aucun | {"catalogs": [...], "truncated": bool}, les catalogues visibles |
list_schemas |
catalog |
{"schemas": [...], "truncated": bool}, les schémas de ce catalogue |
list_tables |
catalog, schema |
{"tables": [...], "truncated": bool}, les tables et vues de ce schéma |
describe_table |
catalog, schema, table, sample_rows (0 à 20) |
les colonnes, leurs types et commentaires, truncated, et un échantillon gouverné si on le demande |
explain_query |
sql |
{"plan": "..."}, le plan d’une requête en lecture, sans l’exécuter |
execute_query |
sql, max_rows (facultatif, inférieur au plafond) |
{"columns": [...], "rows": [...], "row_count": n, "truncated": bool} |
Chaque outil passe par Trino sous le mode d’identité, les outils de liste compris : les métadonnées sont des données, et Trino ne liste que ce que cette identité peut voir. Chaque description d’outil est écrite pour un modèle et dit ce que l’outil rend ; celles de describe_table et execute_query précisent qu’une valeur masquée ou une ligne absente est la politique d’accès, pas une erreur à relancer. Les erreurs reviennent à l’agent sous la forme {"error": "..."}, avec le message entier de Trino ; un refus de droit est une erreur ordinaire, pas un plantage.
La lecture seule
execute_query accepte tout le SQL que Trino accepte, à condition que ce soit une lecture. La requête est analysée en arbre syntaxique, et elle est refusée si elle contient plus d’une instruction, ou si une écriture (INSERT, UPDATE, DELETE, MERGE, CREATE, DROP, ALTER, GRANT, CALL, SET) apparaît n’importe où dans l’arbre, CTE, sous-requêtes et instruction expliquée par un EXPLAIN compris. EXPLAIN ANALYZE est refusé, puisqu’il exécute ce qu’il explique.
Les fonctions de table (TABLE(...)) sont refusées : un TABLE(pg.system.query(query => '...')) se lit comme un simple SELECT mais envoie du SQL brut à la source sous le compte du connecteur, hors de la politique du moteur. Le catalogue system est refusé aussi. Chaque identifiant est validé ([A-Za-z_][A-Za-z0-9_-]*) avant d’entrer dans le SQL. Un nom sans catalogue se lit dans le catalogue de session (TRINO_CATALOG) : donnez-lui un catalogue de données.
La garde est une seconde ligne. La première reste le moteur : donnez au compte du serveur, ou aux utilisateurs, des droits de lecture seulement, et refusez system dans votre politique. Le blog raconte comment cette garde a pris sa forme.
Les bornes
| Borne | Variable | Effet |
|---|---|---|
| Plafond de lignes | TRINO_MAX_ROWS |
une ligne de plus est lue pour dire si la réponse est coupée (truncated), et le reste de la requête est annulé dans Trino |
| Budget d’une requête | TRINO_QUERY_TIMEOUT_SECONDS |
une requête plus longue est annulée dans Trino, et l’agent en est informé |
| Budget d’un appel | MCP_CALL_TIMEOUT_SECONDS |
passé ce délai, aucune requête ne démarre, celle en cours est annulée, et l’agent reçoit une erreur plutôt qu’une réponse partielle |
Le budget est tenu par le serveur, pas posé en propriété de session, pour qu’un moteur de politique qui interdit les propriétés de session ne refuse pas toutes les requêtes.
Le journal
Chaque réponse porte un en-tête X-Request-Id : celui de la passerelle quand il est simple, sinon un identifiant généré. Chaque appel d’outil écrit une ligne JSON dans le journal mcp.audit, indexée par cet identifiant.
{"request_id":"edge-42","tool":"execute_query","subject":"alice","ok":false,"error":"USER_ERROR:PERMISSION_DENIED","duration_ms":84}
Avec la configuration de journal par défaut, la ligne sort précédée de INFO:mcp.audit: ; un collecteur qui attend du JSON pur retire ce préfixe. La ligne dit qui a appelé quel outil, avec quel résultat et en combien de temps. Elle n’a aucun champ qui pourrait contenir le jeton ou le texte SQL. Une erreur y figure sous forme de code, jamais de message, parce qu’un message de Trino peut citer une valeur lue dans une ligne (Cannot cast '<valeur>' to INT). L’agent, lui, lit le message entier. Chaque refus porte un en-tête X-Reason ; en mode strict, une requête sans identité vérifiée reçoit 401 unauthenticated.
Brancher un hôte MCP
À distance, l’hôte se connecte à l’adresse du serveur (/mcp en streamable-http, le transport par défaut ; /sse pour un hôte plus ancien) et envoie le jeton d’accès de l’utilisateur.
{
"mcpServers": {
"trino": {
"url": "https://mcp.example.internal/mcp",
"headers": {"Authorization": "Bearer <le jeton d'accès de l'utilisateur>"}
}
}
}
En local, Claude Desktop, Cursor ou Mistral Vibe lancent eux-mêmes le serveur en stdio. Il n’y a pas de requête HTTP : l’appelant vient de l’environnement, par le jeton d’accès de l’utilisateur dans MCP_USER_TOKEN, vérifié comme un en-tête. En mode strict, le serveur refuse de démarrer sans lui.
{
"mcpServers": {
"trino": {
"command": "uvx",
"args": ["akko-mcp-trino"],
"env": {
"MCP_TRANSPORT": "stdio",
"TRINO_HOST": "trino.example.internal", "TRINO_PORT": "8443", "TRINO_HTTP_SCHEME": "https",
"TRINO_CATALOG": "iceberg", "TRINO_IDENTITY_MODE": "jwt",
"MCP_AUTH_ENABLED": "true", "MCP_AUTH_REQUIRED": "true",
"MCP_JWKS_URL": "https://idp.example.com/realms/data/protocol/openid-connect/certs",
"MCP_OIDC_ISSUER": "https://idp.example.com/realms/data",
"MCP_OIDC_AUDIENCE": "data-platform",
"MCP_USER_TOKEN": "<le jeton d'accès de l'utilisateur>"
}
}
}
}
En mode service, remplacez TRINO_IDENTITY_MODE par TRINO_USER et TRINO_PASSWORD. Filtrez l’en-tête Host avec MCP_ALLOWED_HOSTS quand le serveur est joignable par un navigateur.
Étendre sans modifier
Un paquet Python peut ajouter des outils ou des comportements autour du cœur, sans le modifier. Il s’enregistre sous un groupe de points d’entrée, et l’exploitant le nomme au démarrage.
[project.entry-points."akko_mcp_trino.tools"]
mes-outils = "mon_paquet.outils:register"
export MCP_TOOL_PLUGINS=mes-outils
Rien n’est chargé qui ne soit nommé, et un nom inconnu empêche le démarrage. Un outil ajouté passe par le même client, donc sous le même mode d’identité, avec la même garde de lecture seule et la même ligne de journal. Une extension plus large (MCP_EXTENSIONS, groupe akko_mcp_trino.extensions) peut ajouter des modes d’identité, des gardes à l’entrée, des routes ou des champs de journal, avec une règle : elle peut refuser, jamais élargir.
Configuration
Toute la configuration se lit dans l’environnement ; rien n’est codé en dur.
Trino
| Variable | Rôle | Défaut |
|---|---|---|
TRINO_HOST, TRINO_PORT |
le coordinateur | localhost, 8080 |
TRINO_HTTP_SCHEME |
http ou https ; un mot de passe ou un jeton ne part jamais en http |
http |
TRINO_VERIFY |
vérification TLS : true, false, ou le chemin de l’autorité de Trino |
true |
TRINO_IDENTITY_MODE |
service (chaque requête sous TRINO_USER) ou jwt (chaque requête sous l’appelant, https seulement) ; un mode inconnu empêche le démarrage |
service |
TRINO_USER |
l’identité sous laquelle le serveur lit en mode service |
trino |
TRINO_PASSWORD |
son mot de passe, envoyé en authentification Basic, refusé en http |
aucun |
TRINO_CATALOG |
le catalogue de session, où se lit un nom sans catalogue ; donnez-lui un catalogue de données | system |
TRINO_READ_ONLY |
refuser les écritures dans execute_query |
true |
TRINO_SOURCE |
la source que Trino enregistre pour chaque requête | le nom du serveur |
TRINO_REQUEST_TIMEOUT_SECONDS |
le délai d’une requête HTTP vers Trino | 30 |
TRINO_MAX_ROWS, TRINO_QUERY_TIMEOUT_SECONDS, MCP_CALL_TIMEOUT_SECONDS |
les bornes ci-dessus | 100, 0, 0 |
Identité
| Variable | Rôle | Défaut |
|---|---|---|
MCP_AUTH_ENABLED |
vérifier les jetons des appelants | false |
MCP_AUTH_REQUIRED |
refuser une requête sans identité vérifiée | false |
MCP_JWKS_URL |
le JWKS du fournisseur ; obligatoire quand la vérification est active | aucun |
MCP_OIDC_ISSUER, MCP_OIDC_AUDIENCE |
l’émetteur et l’audience exigés | aucun |
MCP_JWT_LEEWAY_SECONDS |
la tolérance sur exp, nbf et iat, pour l’écart d’horloge |
30 |
MCP_ALLOWED_HOSTS |
le filtre de l’en-tête Host, entrées séparées par des virgules ; vide, tout est servi |
aucun |
Service
| Variable | Rôle | Défaut |
|---|---|---|
MCP_TRANSPORT |
streamable-http, sse ou stdio |
streamable-http |
MCP_PORT, MCP_HEALTH_PORT |
les ports d’écoute | 3000, 3001 |
MCP_SERVER_NAME |
le nom annoncé aux hôtes | trino-mcp |
MCP_USER_TOKEN |
en stdio seulement, le jeton d’accès de l’utilisateur |
aucun |
MCP_TOOL_PLUGINS, MCP_EXTENSIONS |
les greffons et extensions à charger, par nom | aucun |
Compatibilité
| Pris en charge | |
|---|---|
| Python | 3.12, 3.13 |
| Trino | 351 et suivants ; en mode jwt, https avec l’authentification JWT |
| Moteurs de politique | OPA, Ranger, contrôle d’accès fichier de Trino |
| Fournisseurs d’identité | tout fournisseur OIDC qui publie un JWKS (Keycloak, Entra ID, Okta, Dex…) |
| MCP | transports streamable-http, sse et stdio, par le SDK officiel |
| Modèles | tous ; le serveur ne parle jamais à un modèle |
Aller plus loin
Pour d’autres façons de porter l’identité de l’utilisateur, un audit relié au journal du moteur, le contexte métier du catalogue et la gestion des agents et des accès, voyez le serveur livré dans AKKO et ARCHE. Pour une question, écrivez-nous. Pour signaler un problème du cœur, ouvrez une issue.
