Aller au contenu

Serveur MCP

Saiku Cloud embarque un serveur Model Context Protocol (MCP) intégré qui expose vos cubes aux agents LLM sous forme d’outils typés. Les agents découvrent quels cubes sont disponibles, interrogent leur structure, et exécutent des requêtes — le tout via une petite surface d’outils validée qui les empêche d’inventer des noms de mesures ou d’halluciner des références de colonnes.

L’endpoint MCP se trouve à https://api.saiku.bi/rest/saiku/api/mcp. Il parle MCP en streamable-HTTP (la version actuelle du protocole) et prend en charge deux moyens d’authentification :

  • Connecteur OAuth 2.1recommandé pour les clients de chat (Claude Desktop, claude.ai, Cursor — tout ce qui dispose d’une option « connecteur personnalisé » / « MCP distant »). Collez l’URL et le client connecte l’utilisateur ; il n’y a aucune clé à copier ou créer. En libre-service, disponible sur Team et au-delà.
  • Clé API Bearerpour les clients programmatiques / SDK. La même clé sk_... que tous les autres endpoints de cette surface.

Voir Connexion d’un agent pour les deux.

Pourquoi MCP plutôt que du SQL pur ?

Les LLMs sont notoirement mauvais pour écrire du SQL analytique. Sur Spider 2.0, le benchmark standard de text-to-SQL en conditions réelles, les meilleurs modèles frontière obtiennent environ 24 %. Les échecs ne sont pas subtils — noms de colonnes inventés, mauvaises jointures, tables hallucinées, schémas confondus entre bases de données.

Un cube Saiku empêche structurellement cette classe d’échecs :

  • L’agent choisit les mesures et dimensions par nom à partir d’un schema auto-descriptif.
  • La validation s’exécute côté serveur. Si l’agent invente un nom, nous renvoyons un 400 structuré listant les alternatives valides.
  • L’agrégation, les jointures et les totaux font partie de la définition du cube — l’agent ne compose pas de jointures et ne choisit pas d’agrégations, il choisit simplement quelles mesures et dimensions il veut.

Le résultat est une surface analytique que les LLMs peuvent utiliser de manière fiable en production, et pas seulement en démo.

La poignée de main

MCP est du JSON-RPC 2.0 sur HTTP. Le flux complet :

  1. Le client envoie initialize (aucun en-tête de session requis).
  2. Le serveur répond avec serverInfo et un ID de session dans l’en-tête de réponse Mcp-Session-Id.
  3. Le client inclut Mcp-Session-Id dans chaque requête ultérieure.
  4. Le client appelle tools/list pour découvrir les outils, puis tools/call pour les invoquer.

Un initialize minimal :

Fenêtre de terminal
curl -X POST https://api.saiku.bi/rest/saiku/api/mcp \
-H "Authorization: Bearer $SAIKU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "my-agent", "version": "1.0" }
}
}'

La réponse inclut l’en-tête d’ID de session — extrayez-le de Mcp-Session-Id et envoyez-le sur chaque appel suivant.

Si vous utilisez un client MCP de haut niveau (les SDKs officiels gèrent la poignée de main pour vous), tout cela est invisible. Configurez le client avec l’URL + votre clé Bearer et ça fonctionne.

Les six outils

Le serveur expose six outils, conçus pour être l’ensemble minimal utile au travail analytique.

ID de cube

Chaque outil qui nomme un cube prend un ID de cube au format connectionName/catalog/schema/cubeName. Vous obtenez les parties depuis la réponse de list_cubes. Exemple :

cloud__62e7bf54__v1__foodmart-globex-demo/FoodMart/FoodMart/Sales

Utilisez cette chaîne complète séparée par des barres obliques comme argument cube partout où les outils ci-dessous en attendent un.

list_cubes

Liste tous les cubes OLAP que l’utilisateur courant peut interroger. Toujours votre premier appel quand vous ne savez pas ce qui est disponible.

{
"name": "list_cubes",
"arguments": {}
}

Réponse :

{
"cubes": [
{
"connectionName": "cloud__62e7bf54__v1__foodmart-globex-demo",
"catalog": "FoodMart",
"schema": "FoodMart",
"cubeName": "Sales",
"cubeCaption": "Sales",
"defaultMeasure": "Unit Sales",
"measureCount": 8
}
]
}

Renvoie jusqu’à quelques dizaines d’entrées. Non paginé — les cubes Saiku se comptent par dizaines par tenant, pas par milliers.

describe_cube

Obtient la structure interrogeable complète d’un cube. Appelez toujours cela avant run_query si vous n’avez pas encore vu la structure du cube — il vous indique exactement quels noms sont valides et inclut des corps de requête d’exemple prêts à l’emploi.

{
"name": "describe_cube",
"arguments": {
"cube": "cloud__62e7bf54__v1__foodmart-globex-demo/FoodMart/FoodMart/Sales"
}
}

Renvoie les mesures (indexées par nom en minuscules), les dimensions, les hiérarchies, les niveaux, des membres d’exemple avec des noms uniques MDX (style [Customer].[Customers].[USA].[CA].[San Diego]), ainsi qu’un bloc requestSchema + examples prêt à l’emploi pour l’outil run_query.

search_members

Trouve les noms uniques MDX des membres d’un niveau par correspondance de sous-chaîne. À utiliser lorsque le cube a plus de membres à un niveau que l’échantillon de describe_cube n’en couvre (par exemple, rechercher une ville, un client ou une marque de produit spécifique), ou lorsque l’utilisateur dit « filtrer par Italie » et que vous devez confirmer l’orthographe.

{
"name": "search_members",
"arguments": {
"cube": "cloud__62e7bf54__v1__foodmart-globex-demo/FoodMart/FoodMart/Sales",
"dimension": "Customers",
"hierarchy": "Customers",
"level": "City",
"q": "San",
"limit": 25
}
}

hierarchy est requis lorsque la dimension en a plus d’une (fréquent — les dimensions Time en ont typiquement plusieurs). Pour les dimensions à hiérarchie unique, vous pouvez l’omettre.

Renvoie jusqu’à limit résultats avec caption, name et uniqueName.

run_query

L’outil principal. La plupart des questions utilisateur aboutissent ici. Construisez la requête à partir de la structure issue de describe_cube ; le serveur valide chaque nom et renvoie un VALIDATION_ERROR structuré avec les alternatives valides si un nom est erroné, donc ne pré-validez pas vous-même.

{
"name": "run_query",
"arguments": {
"cube": "cloud__62e7bf54__v1__foodmart-globex-demo/FoodMart/FoodMart/Sales",
"measures": [
{ "name": "Unit Sales" },
{ "name": "Store Sales" }
],
"rows": [
{ "dimension": "Time", "hierarchy": "Time", "level": "Year" }
],
"columns": [
{ "dimension": "Customers", "hierarchy": "Customers", "level": "Country",
"members": ["[Customer].[Customers].[USA]"] }
],
"filters": [
{ "dimension": "Promotions", "hierarchy": "Promotions", "level": "Promotion Name",
"members": ["[Promotion].[Promotions].[No Promotion]"] }
],
"limit": 100
}
}

Règles de forme tirées du requestSchema en direct :

  • measures — tableau d’objets. Chaque entrée a un name correspondant à la légende brute de la map measures de describe_cube (par ex. "Unit Sales", pas "[Measures].[Unit Sales]").
  • rows / columns / filters — tableau de sélections d’axe. Chaque entrée a dimension et level (requis), hierarchy (requis lorsque la dimension en a plus d’une), et un tableau members facultatif de noms uniques MDX auxquels restreindre.
  • cube — la chaîne complète d’ID de cube (voir ci-dessus) ou un objet {connectionName, catalog, schema, cubeName}.
  • format"records" (par défaut) ou "matrix". Les agents veulent presque toujours records.
  • limit — plafond de lignes. Par défaut 100 ; max 10 000 sur le chemin Mondrian.

Réponse :

{
"status": "SUCCESS",
"queryId": "eb623b5f-f32d-4b84-96fa-2bde148556a0",
"runtimeMs": 196,
"totalRows": 1,
"data": [
{
"Year": "1997",
"Unit Sales": {
"value": 266773.0,
"formatted": "266,773",
"properties": { "formatString": "Standard", "datatype": "Numeric" }
},
"Store Sales": {
"value": 565238.13,
"formatted": "565,238.13",
"properties": { "formatString": "#,###.00", "datatype": "Numeric" }
}
}
]
}

L’enveloppe de cellule ({value, formatted, properties}) porte à la fois la valeur numérique analysée et la chaîne d’affichage formatée par Mondrian. Utilisez value pour les calculs, formatted pour l’affichage.

Le queryId dans la réponse peut être passé à drillthrough pour inspecter les lignes sous-jacentes derrière une cellule.

preview_query

Compile une requête en MDX sans l’exécuter. À utiliser quand vous voulez montrer à l’utilisateur ce que la requête fera, auditer une requête générée, ou estimer le coût avant d’exécuter une agrégation coûteuse.

{
"name": "preview_query",
"arguments": {
"cube": "cloud__62e7bf54__v1__foodmart-globex-demo/FoodMart/FoodMart/Sales",
"measures": [{ "name": "Unit Sales" }],
"rows": [{ "dimension": "Time", "hierarchy": "Time", "level": "Year" }]
}
}

Réponse :

{
"status": "PREVIEW",
"queryId": "f4e2a890-…",
"generatedMdx": "SELECT NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\nNON EMPTY [Time].[Time].[Year].Members ON ROWS\nFROM [Sales]"
}

La validation s’exécute comme pour run_query — preview renvoie la même forme VALIDATION_ERROR si les noms ne se résolvent pas.

drillthrough

Récupère les lignes brutes de la table de faits derrière une requête spécifique. À utiliser quand l’utilisateur demande « montrez-moi les transactions sous-jacentes » ou veut inspecter le détail d’une seule cellule.

{
"name": "drillthrough",
"arguments": {
"queryId": "eb623b5f-f32d-4b84-96fa-2bde148556a0",
"maxrows": 100
}
}

Passez le queryId renvoyé par un appel run_query antérieur (c’est le champ queryId dans la réponse, pas l’id de l’enveloppe JSON-RPC). Les cellules de la réponse utilisent la même enveloppe typée que run_query.

Erreurs de validation

Chaque outil qui accepte des noms de cube / mesure / dimension exécute une validation côté serveur. Lorsque quelque chose ne se résout pas, vous obtenez une erreur structurée plutôt qu’un 500 générique :

{
"isError": true,
"structuredContent": {
"code": "VALIDATION_ERROR",
"message": "Unknown measure: [Measures].[Reveue]",
"field": "measures[0]",
"alternatives": [
"[Measures].[Revenue]",
"[Measures].[Repeat Revenue]"
]
}
}

Le champ alternatives est la fonctionnalité-clé pour les agents LLM — quand le modèle se rappelle mal d’un nom, le serveur lui indique les options valides les plus proches, et l’agent peut s’auto-corriger en un essai au lieu de deviner.

Connexion d’un agent

Clients de chat — connecteur OAuth (recommandé)

Les clients qui prennent en charge les connecteurs MCP personnalisés / distants (Claude Desktop, claude.ai, Cursor, et d’autres) se connectent sans clé API — ils authentifient l’utilisateur via OAuth à la place. Ajoutez l’URL MCP comme connecteur personnalisé et le client fait le reste :

  1. Dans les paramètres de connecteur du client, ajoutez un serveur MCP personnalisé/distant avec l’URL https://api.saiku.bi/rest/saiku/api/mcp. Laissez les champs optionnels « client ID / secret » vides.
  2. Le client découvre automatiquement les endpoints OAuth de Saiku Cloud et s’enregistre — aucun client ID ou secret à créer (Dynamic Client Registration).
  3. Un navigateur s’ouvre pour la connexion à Saiku Cloud (votre connexion habituelle à l’espace de travail), puis affiche un écran de consentement nommant le client et l’accès qu’il demande (interrogation de cubes en lecture seule). Approuvez-le.
  4. Le connecteur est actif — l’agent peut appeler list_cubes et les autres outils immédiatement.

L’autorisation d’un connecteur nécessite un espace de travail Team, Business ou Enterprise. L’identifiant que reçoit le client est limité à MCP en lecture seule pour votre espace de travail et rien d’autre ; il ne voit jamais vos identifiants d’entrepôt, d’autres tenants ou une quelconque surface d’écriture.

Gestion des connecteurs. Chaque client autorisé apparaît sous Connexions → Agents connectés dans le dashboard, où vous pouvez en révoquer n’importe lequel en un clic. Les jetons d’accès sont de courte durée (1 heure) et se rafraîchissent silencieusement ; une révocation invalide immédiatement le jeton de rafraîchissement, donc l’accès s’arrête au plus tard dans l’heure — généralement tout de suite.

Programmatique — clé API Bearer

Pour votre propre code, vos frameworks d’agent, ou tout client qui injecte un en-tête statique, créez une clé API dans le dashboard (Clés API, limitez-la à MCP) et passez-la comme jeton Bearer :

{
"mcpServers": {
"saiku": {
"url": "https://api.saiku.bi/rest/saiku/api/mcp",
"headers": {
"Authorization": "Bearer sk_..."
}
}
}
}

Via les SDKs MCP officiels : instanciez un StreamableHttpClientTransport sur l’URL avec l’en-tête Bearer, puis appelez l’API client MCP standard.

Après connexion (de l’une ou l’autre manière), le prompt de votre agent n’a généralement besoin que d’une ligne — « Vous avez accès à un serveur MCP saiku avec des outils pour interroger des cubes analytiques. Utilisez list_cubes pour commencer. » Tout le reste est l’agent qui découvre et utilise les outils tels qu’ils sont décrits dans le schema.

Limites de débit

Le trafic MCP est imputé au budget standard de limites de débit de votre tenant (voir Authentification). Les agents lourds sur le niveau Starter peuvent atteindre la limite ; envisagez Team ou Business pour les charges de travail d’agents en production.

Ce que MCP n’expose pas

La surface MCP est en lecture seule. Les outils qui modifient l’état — enregistrer des classeurs, créer des schemas, ajouter des connexions, modifier les définitions de cube — ne sont pas exposés via MCP. Ils vivent sur le dashboard ou sur l’API REST, où un humain est dans la boucle.

C’est délibéré : un agent qui peut lire vos données mais ne peut pas modifier vos schemas ne peut jamais casser accidentellement votre configuration analytique. Si vous voulez un agent qui crée des cubes, utilisez l’API d’inférence IA et placez une étape de revue humaine au milieu.

Et ensuite