Metabase
officielServeur MCP officiel de Metabase pour rechercher des données, construire des requêtes sur la couche sémantique et visualiser les résultats via des clients MCP.
Que pouvez-vous faire avec Metabase MCP ?
- Rechercher et récupérer du contenu — Demandez à votre assistant de trouver des questions, des tableaux de bord ou des modèles dans Metabase à l’aide de l’outil
search, puis récupérez les détails avecget_content. - Exécuter des requêtes et visualiser les résultats — Faites exécuter par votre assistant des requêtes structurées via
execute_queryou du SQL brut avecexecute_sql, puis affichez les résultats sous forme de graphiques interactifs à l’aide devisualize_query. - Créer et gérer des tableaux de bord — Demandez à votre assistant de créer ou de mettre à jour des tableaux de bord et leurs mises en page avec
dashboard_write, ou de dupliquer du contenu existant dans des collections viaduplicate_content. - Configurer des alertes et des abonnements — Demandez à votre assistant de créer des alertes planifiées pour les questions enregistrées avec
alert_writeou de configurer des abonnements par e-mail aux tableaux de bord à l’aide desubscription_write. - Explorer la hiérarchie des données — Dirigez votre assistant pour parcourir les bases de données, schémas, tables et champs via
browse_data, ou naviguer dans les collections de manière structurelle avecbrowse_collection.
Documentation
Serveur MCP Metabase
Metabase inclut un serveur Model Context Protocol (MCP) intégré qui permet aux clients IA de se connecter directement à une instance Metabase. Il utilise le transport HTTP streamable et s'appuie sur l'API Agent de Metabase pour exposer des outils de recherche, navigation, requêtage, visualisation et création/mise à jour de contenu - le tout limité aux permissions de l'utilisateur connecté.
Point de terminaison
Le serveur MCP est disponible à l'adresse :
https://{your-metabase.example.com}/api/metabase-mcp
L'ancien chemin /api/mcp fonctionne toujours comme alias pour les clients existants, mais /api/metabase-mcp est l'
URL canonique à annoncer.
Connexion d'un client
Pointez n'importe quel client compatible MCP vers le point de terminaison /api/metabase-mcp. Par exemple, avec Claude Code :
claude mcp add metabase https://{your-metabase.example.com}/api/metabase-mcp --transport streamable-http
Pour Claude Desktop, créez un connecteur personnalisé utilisant la même URL.
Pour Cursor, ouvrez Settings > MCP et ajoutez un nouveau serveur avec le type défini sur streamable-http et l'URL :
https://{your-metabase.example.com}/api/metabase-mcp
Authentification
Les clients MCP s'authentifient via OAuth 2.0. Metabase exécute son propre serveur OAuth intégré - aucun fournisseur externe n'est nécessaire.
Le flux pour une première connexion :
- Le client découvre les points de terminaison OAuth de Metabase.
- Le client s'enregistre auprès de Metabase.
- L'utilisateur est redirigé vers Metabase pour se connecter et approuver la connexion.
- Le client reçoit un jeton d'accès limité aux permissions Metabase de l'utilisateur.
Les sessions basées sur navigateur (authentification par cookie), les clés API et les jetons mb:full n'ont pas de portée MCP, ils peuvent donc se connecter mais
ne peuvent appeler aucun outil limité.
Portées
Les jetons d'accès sont limités pour restreindre les outils qu'un client peut utiliser :
| Portée | Outils accordés |
|---|---|
agent:content:read | browse_collection, browse_data, get_content, get_parameter_values, glossary, learn, search, et la ressource de données catalog://metabase/fields (voir Ressources) |
agent:content:write | bookmark_content, collection_write, dashboard_write, document_write, duplicate_content, measure_write, metric_write, question_write, segment_write |
agent:delivery:write | alert_write, subscription_write |
agent:query:run | execute_query, refresh_ui_credential, render_drill_through, run_saved_question, visualize_query |
agent:sql:run | execute_sql |
Un outil correspond uniquement à la portée exacte du tableau. Un caractère générique tel que * ou agent:*, et la sentinelle ::unrestricted,
ne sont pas des portées MCP, ils n'accordent donc aucun outil.
Ces portées ne limitent que la surface MCP. Aucun point de terminaison REST ne peut déclarer l'une d'elles comme son :scope, donc un nouveau jeton émis pour un
client MCP n'accorde rien sur l'API REST.
Le scopes_supported des métadonnées de ressource protégée et le scope du défi 401 listent tous deux chaque portée du
tableau, donc une nouvelle connexion demande toutes ces portées. Certains clients ne montent pas en privilège après un 403 : une reconnexion manuelle
depuis les paramètres du connecteur Claude enregistre un nouveau client et demande exactement ce que ces listes contiennent. Lorsqu'elles ne listaient que
la portée de base, cette reconnexion n'offrait jamais l'écriture, le SQL brut ou la livraison, et l'utilisateur n'avait aucun moyen de les accorder
(ENG-11089). Les métadonnées du serveur d'autorisation les annoncent également toutes.
La portée de base (agent:content:read agent:query:run) est verrouillée sur l'écran de consentement : lorsqu'un client demande l'une de
ces portées, l'utilisateur ne peut pas la décocher. agent:query:run fait partie de la portée de base afin que les graphiques n'aient jamais besoin d'une montée en privilège. Claude
Desktop réessaie un outil après une montée en privilège sur une session qui ne déclare pas la prise en charge des applications MCP, donc un
visualize_query monté en privilège est refusé et son graphique ne s'intègre jamais.
Un appel d'outil ou une lecture de ressource de données pour lequel le jeton ne possède pas la portée est refusé avec un HTTP 403 et un
défi WWW-Authenticate: Bearer error="insufficient_scope" dont le scope liste les portées v2 que le jeton possède déjà
plus celle requise, afin qu'un client puisse monter en privilège. Chaque outil déclare également sa portée dans securitySchemes, qui est le
brouillon SEP-1488, pris en charge par ChatGPT. Cela ne fait
pas partie de MCP 2025-03-26 (la version que ce serveur annonce) ni de la spécification finale des outils 2026-07-28, donc les autres clients
découvrent la portée manquante à partir du 403 à la place. Dans un lot JSON-RPC, le refus est une erreur -32600 en bande
à la place. Les lectures de l'interface shell ne sont jamais contestées : voir Ressources.
Les métadonnées de ressource protégée OAuth sont disponibles à l'adresse :
/.well-known/oauth-protected-resource/api/metabase-mcp
Sur l'écran de consentement, chaque portée demandée par le client commence cochée, et les portées de base sont également verrouillées. Seules
les portées laissées cochées sont accordées, et uniquement pour le jeton que cette autorisation émet. Une décochage ne touche jamais un jeton
que l'application possède déjà. Une portée laissée décochée n'est pas mémorisée par Metabase. Un 403 ultérieur peut déclencher une autre montée en privilège dans
les clients qui la prennent en charge. D'autres clients peuvent nécessiter une réautorisation manuelle. Le error_description de chaque défi
se termine par une note indiquant que l'utilisateur doit accorder la permission sur l'écran de consentement.
Plusieurs clients remplacent le error_description du 403 par leur propre texte, donc le initialize du résultat
instructions explique également les échecs de portée au modèle : une erreur d'authentification signifie généralement une permission manquante plutôt qu'une
connexion expirée, le modèle doit nommer l'outil ou la ressource en échec et la permission requise, et l'utilisateur l'accorde en
se reconnectant et en laissant la permission cochée sur l'écran de consentement. Les instructions sont une chaîne statique, la même
pour chaque appelant : il n'y a pas de liste de permissions par connexion.
Accès aux outils par groupe
Sur les instances Enterprise avec la fonctionnalité ai_controls, un administrateur peut restreindre la surface par groupe de permissions dans Admin > IA > Contrôles d'utilisation > Accès aux outils MCP (affiché chaque fois que le serveur MCP est activé, avec ou sans fournisseur LLM). Chaque groupe a une ligne dans mcp_group_permission indiquant si ses membres peuvent se connecter et, par nom d'outil, le "yes" ou "no" explicite de l'administrateur ; un outil sans entrée prend la valeur par défaut déclarée par son auteur, donc chaque deftool porte un :default-access requis de :allowed ou :denied et un outil publié plus tard reçoit exactement cette valeur par défaut dans chaque groupe jusqu'à ce qu'un administrateur la remplace. Les entrées sont indexées par nom d'outil, donc renommer un outil nécessite :renamed-from avec ses anciens noms (une entrée stockée sous un ancien nom s'applique toujours et l'API admin la présente sous le nom actuel), ou l'auteur accepte que chaque choix d'administrateur pour cet outil soit abandonné ; metabase.mcp.v2.tool-names-test épingle les noms actifs pour que rien de tout cela n'arrive par accident. La politique effective d'un utilisateur est le tool_access de chaque groupe auquel il appartient dont la ligne active MCP : MCP est activé lorsqu'un tel groupe existe, et un outil est autorisé lorsque l'un de ces groupes le résout à oui. Les lignes seules décident, et elles enregistrent également le mode de permission : mode simple (Administrateurs, Tous les utilisateurs et Tous les utilisateurs locataires) ou mode niveau groupe (chaque autre groupe), qui est l'état dans lequel Tous les utilisateurs n'a pas de ligne. Un interrupteur supprime les lignes des groupes que le nouveau mode masque et initialise les groupes qu'il active à l'entrée sans ligne (Analystes de données à l'entrée du mode niveau groupe, Tous les utilisateurs et Tous les utilisateurs locataires à la sortie), et PUT refuse un groupe que le mode actuel masque, donc aucune requête ne lit un mode en cache. La politique est appliquée en plus des portées OAuth dans tools/list et tools/call (v2/registry.clj), et un utilisateur dont les groupes n'accordent aucun accès est refusé à initialize et à resources/read (pour les sessions ouvertes avant la désactivation de MCP) avec une erreur nommant la solution. Les outils exclusivement applicatifs (_meta {:ui {:visibility ["app"]}}, c'est-à-dire refresh_ui_credential) ne sont pas sur la page admin : ils sont autorisés tant que la politique autorise un outil d'applications MCP. Les superutilisateurs et les appelants internes ne sont jamais restreints ; OSS autorise tout. Sur Enterprise, les lignes stockées sont appliquées même sans la fonctionnalité ai_controls (un jeton expiré ou invérifiable conserve les restrictions configurées par un administrateur) ; la fonctionnalité ne contrôle que la page admin et l'API. Le résolveur est permissions.clj (defenterprise, implémentation EE dans metabase-enterprise.mcp.permissions) et l'API admin est /api/ee/ai-controls/mcp-permissions.
Outils disponibles
Générés à partir du registre v2 (deftool). La portée nommée ici est celle que le registre vérifie avant que l'outil
ne s'exécute ; certains gestionnaires vérifient une portée supplémentaire une fois qu'ils savent ce que fait l'appel - agent:sql:run lorsqu'une source
se résout en SQL natif (question_write), et agent:query:run pour l'exécution qu'une alerte ou un abonnement diffère
(alert_write, subscription_write). Ces refus portent le même défi 403 insufficient_scope. tools/list
affiche chaque outil quel que soit le jeton détenu, et un jeton sans la portée ne peut pas l'appeler.
| Outil | Portée | Description |
|---|---|---|
alert_write | agent:delivery:write | Créer ou mettre à jour une alerte : une notification envoyée selon un calendrier lorsque les résultats d'une question enregistrée répondent à une condition. |
bookmark_content | agent:content:write | Ajouter ou supprimer un favori sur du contenu pour l'utilisateur appelant — la même liste d'étoiles/favoris que la barre latérale de Metabase affiche. |
browse_collection | agent:content:read | Parcourir les collections de manière structurelle — un identifiant uniforme sur chaque partition : un identifiant numérique, un entity_id de 21 caractères, « root » (ré-enraciné par espace de noms), ou « trash » (contenu archivé, mode éléments uniquement). |
browse_data | agent:content:read | Parcourir la hiérarchie des données : bases de données → schémas → tables → champs. |
collection_write | agent:content:write | Créer, renommer, déplacer, archiver ou restaurer une collection — les dossiers qui contiennent les questions, tableaux de bord, modèles et documents. |
dashboard_write | agent:content:write | Créer ou mettre à jour un tableau de bord et modifier sa mise en page avec des opérations ordonnées. |
document_write | agent:content:write | Créer ou mettre à jour un document. |
duplicate_content | agent:content:write | Copier une question, un tableau de bord ou un document dans une collection — plus économique et plus sûr que de lire l'original et de le recréer, et cela préserve tout ce que les projections de lecture omettent. |
execute_query | agent:query:run | La méthode par défaut pour répondre à une question à partir de données : valider et exécuter une requête structurée (MBQL), renvoyant les lignes plus un query_handle. |
execute_sql | agent:sql:run | Échappatoire : exécuter une chaîne SQL brute contre une base de données, renvoyant les lignes plus un query_handle. |
get_content | agent:content:read | Récupérer du contenu par {type, id} — la lecture typée pour tout ce qui est trouvé via la recherche ou browse_collection. |
get_parameter_values | agent:content:read | Récupérer les valeurs valides pour un filtre sur un tableau de bord ou une question enregistrée, afin de filtrer avec des valeurs réelles au lieu de deviner. |
glossary | agent:content:read | Rechercher un terme métier tel que cette instance Metabase le définit ; les noms de termes définis accompagnent la propre description de l'outil. |
learn | agent:content:read | Lire la documentation des tâches (compétences) de ce serveur pour les dialectes d'écriture que les schémas ne peuvent pas décrire entièrement. |
measure_write | agent:content:write | Créer ou mettre à jour une mesure : une agrégation MBQL nommée et réutilisable attachée à une table, référencée dans l'agrégation d'une autre requête comme ["measure", id]. |
metric_write | agent:content:write | Créer ou mettre à jour une métrique : une agrégation enregistrée et réutilisable qui vit dans une collection et peut être interrogée seule ou référencée depuis d'autres requêtes. |
question_write | agent:content:write | Créer, mettre à jour ou archiver une question ou un modèle enregistré. |
refresh_ui_credential | agent:query:run | Actualiser l'identifiant de connexion limité utilisé par une application MCP Metabase. |
render_drill_through | agent:query:run | Rendre la visualisation de forage (drill-through) dans laquelle l'utilisateur vient de naviguer. |
run_saved_question | agent:query:run | Exécuter une question enregistrée (carte) par identifiant numérique ou entity_id, renvoyant les lignes en ligne. |
search | agent:content:read | Trouver du contenu dans l'instance Metabase par pertinence. |
segment_write | agent:content:write | Créer ou mettre à jour un segment : un filtre MBQL nommé et réutilisable attaché à une table, référencé depuis les filtres d'autres requêtes. |
subscription_write | agent:delivery:write | Créer ou mettre à jour un abonnement à un tableau de bord — livraison planifiée d'un tableau de bord entier, par ex. |
visualize_query | agent:query:run | Visualiser une requête sous forme de graphique interactif ou de tableau, rendu en ligne dans la conversation. |
execute_query renvoie row_limit lignes par appel (défaut 100, max 2000) — une taille de page, pas une limite sur le résultat.
Une page tronquée signale truncated: true et, lorsque la requête a un ordre total, un next_cursor à renvoyer comme
cursor pour la page suivante, jusqu'à ce qu'une page arrive avec truncated: false. Une requête qui ne veut que ses N premières lignes
porte limit: N dans son étape (avec un order-by) ; le serveur dépense cette limite sur les pages, donc la dernière
page revient complète sans curseur et la pagination se termine d'elle-même.
Ressources
Le serveur expose des ressources MCP afin que les clients puissent récupérer du contenu supplémentaire par URI sans gonfler les descriptions d'outils.
| URI de ressource | Portée | Description |
|---|---|---|
ui://metabase/visualize-query.html | agent:query:run (identifiant UI uniquement) | La coque iframe des applications MCP vers laquelle visualize_query pointe un client capable. |
ui://metabase/render-drill-through.html | agent:query:run (identifiant UI uniquement) | La coque vers laquelle render_drill_through pointe. |
catalog://metabase/fields | agent:content:read | Les chemins en pointillés que chaque type de contenu accepte dans les arguments fields. |
Chaque ressource est listée quels que soient les périmètres du jeton. Une ressource de données, comme le catalogue des champs, n'est lue que par un
jeton détenant son périmètre ; sinon, la lecture obtient le défi 403 insufficient_scope décrit sous
Périmètres. Une coque UI est lue par tout jeton, car un hôte d'applications MCP lit la coque d'un outil
en même temps que l'appel d'outil, et si cette lecture était refusée, l'hôte ne monterait pas en gamme après le 403 de l'appel d'outil. Les
coques ne portent aucune donnée. Une coque lue par un jeton sans le périmètre de la coque ne frappe jamais un identifiant UI. Les données du graphique sont
contrôlées par l'appel d'outil et par refresh_ui_credential (les deux agent:query:run), dont l'iframe a besoin avant de pouvoir
interroger quoi que ce soit. Une URI inconnue est une erreur -32602 « Resource not found ».
Les packs de compétences sont livrés via l'outil learn plutôt que comme ressources.
La navigation des entités se fait avec les outils get_content et browse_* plutôt qu'un schéma d'URI ; les ressources ui://
ci-dessus n'existent que pour qu'un client capable de rendre une iframe ait quelque chose à monter.
Méthodes JSON-RPC prises en charge
| Méthode | Description |
|---|---|
initialize | Initialiser la connexion MCP. Renvoie les capacités du serveur et un ID de session. |
notifications/initialized | Notification client que l'initialisation est terminée. |
tools/list | Lister les outils disponibles, quels que soient les périmètres du jeton. |
tools/call | Appeler un outil avec des arguments. |
resources/list | Lister les ressources disponibles, quels que soient les périmètres du jeton. |
resources/read | Lire une ressource par URI ; une ressource de données nécessite son périmètre. Nécessite une session. |
ping | Ping de maintien en vie. |
Les requêtes peuvent être envoyées individuellement ou comme un lot JSON-RPC. Le serveur répond en JSON ou SSE selon l'en-tête
Accept.
Architecture
L'implémentation vit dans ces fichiers :
-
transport.clj- Le transport HTTP. Analyse les requêtes JSON-RPC, valide l'authentification et les en-têtes de session, applique les vérifications d'origine (protection contre le rebinding DNS) et répartit vers la méthode appropriée. Prend en charge les formats de réponse JSON et SSE. -
v2/api.clj- Le gestionnaire de surface d'outils. Connecte le transport aux registres d'outils et de ressources v2 et définit la répartition des méthodes (tools/list,tools/call,resources/list,resources/read,ping). -
v2/registry.clj- Le registre d'outils v2. Les outils s'enregistrent eux-mêmes viadeftool; le registre vérifie les périmètres, valide les arguments, répartit les appels et enregistre l'utilisation. -
scope.clj- La logique de correspondance des périmètres. Correspondances exactes uniquement : les jokers, les périmètres nuls et le sentinelle::unrestrictedne correspondent à rien.
Flux de requêtes
MCP client
-> POST /api/metabase-mcp (JSON-RPC)
-> Origin + session validation
-> Auth: OAuth bearer token or browser session
-> Scope check against requested tool
-> Synthetic request to Agent API endpoint
-> Response materialized as MCP content
-> JSON or SSE back to client