Comment nous avons ouvert notre backend aux LLM avec MCP

Par Steven Watremez Mis à jour le 18 février 2026 12 min. de lecture
serveur ia

Les LLMs deviennent utiles quand ils lisent vos données et déclenchent des actions, sans bricolage ni prise de risque. Le Model Context Protocol (MCP) standardise la passerelle entre un assistant et votre backend: des outils (tools) déclarés, découverts à la volée, permissionnés et auditables. Cet article propose de faire découvrir comment fonctionne un serveur MCP et d’offrir des pistes de réflexion pour savoir quoi exposer (lecture sûre vs actions), comment maîtriser la taille des réponses pour contrôler les tokens, et quels garde‑fous poser (OAuth/OIDC, scopes, quotas, relance sans doublon, audit).

Le MCP expliqué en 2 minutes

MCP est un protocole. Le client (assistant/IDE) ouvre une session, découvre vos capacités et décrit les appels possibles. Le serveur MCP expose des tools (actions) et des resources (données) au travers de schémas clairs qui précisent entrées/sorties et erreurs. Les prompts et la session encadrent l’échange et permettent aux clients de cadrer l’usage attendu.

Côté positionnement, MCP ne remplace pas votre logique métier : il standardise la façon d’annoncer et d’appeler des outils, à la manière de LSP (Language Server Protocol) pour les IDE : LSP permet à des éditeurs (VS Code, Vim, IntelliJ) de dialoguer avec des « serveurs de langage » (TypeScript, Python, etc.) via un protocole commun pour l’autocomplétion, les diagnostics et la navigation. Cela réduit les intégrations ad hoc, améliore l’interopérabilité et facilite la sécurité et l’observabilité.

Le flux minimal ressemble à ceci :

  • ouverture de session
  • découverte des tools
  • proposition d’appel par le LLM
  • validation côté client
  • exécution côté serveur
  • réponse compacte.
flux schema MCP

À chaque étape, vous gardez la main sur les permissions, les limites de temps et de taille, et le format des retours.

Découverte vs exécution

Avant toute action, le client passe par une phase de découverte avec tools/list : il récupère la liste des outils exposés par le serveur ainsi que leurs schémas JSON (contrats de validation JSON). Cela fournit un inventaire clair des outils disponibles et des paramètres attendus; le LLM et l’application savent alors ce qui est faisable et avec quels paramètres.

Une fois un outil pertinent identifié, on passe à l’exécution avec tools/call : le client appelle ce tool par son nom en fournissant des arguments validés; le serveur renvoie alors un result conforme au schéma, ou bien un error structuré si quelque chose ne va pas. Les exemples ci‑dessous illustrent d’abord la découverte (tools/list), puis l’exécution (tools/call).

Exemple tools/list :

// Appel de découverte des outils (tools/list): le client récupère l’inventaire des tools exposés
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "tools/list", // méthode de découverte
  "params": {} // aucun paramètre requis
}
// Réponse (extrait): inventaire des outils et de leurs schémas d'entrée
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "tools": [
      {
	      // nom du tool (utilisé dans tools/call)
        "name": "searchFlights", 
        // à quoi sert l'outil (recherche d'options de vol)
        "description": "Search for available flights", 
        // schéma JSON des arguments attendus par le tool
        "inputSchema": { 
          "type": "object",
          "properties": {
	          // ville/code aéroport d'origine (ex: "NYC")
            "origin": { 
	            "type": "string" 
            }, 
            // ville/code aéroport de destination (ex: "BCN")
            "destination": { 
	            "type": "string"
            }, 
            "date": { // date de départ (ISO 8601, YYYY-MM-DD)
	            "type": "string", 
	            "format": "date" 
	          }
          },
          "required": ["origin", "destination", "date"] // champs obligatoires
        }
      }
    ]
  }
}
icon alerte

Bon à savoir

Un client appelle un tool par son nom via tools/call avec des arguments JSON validés; le serveur renvoie un objet JSON conforme au schéma attendu via result, ou un objet error structuré. Voir aussi tools/list pour la découverte.

Exemple tools/call

// Appel d’un tool (client → serveur)
{
  "jsonrpc": "2.0",
  "id": 1,
  // méthode appelée
  "method": "tools/call", 
  "params": {
	  // nom exact du tool
    "name": "searchFlights",
    // paramètres validés selon le schéma d’entrée
    "arguments": { 
	    "origin": "NYC", 
	    "destination": "BCN", 
	    "date": "2025-06-15" 
	   } 
  }
}
// Réponse OK (serveur → client)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "items": [
      { 
	      "id": "FL-1234", 
	      "airline": "Iberia", 
	      "price": "350 €", 
	      "departAt": "2025-06-15T10:10:00Z", 
	      "arriveAt": "2025-06-15T22:05:00Z" 
	     }
    ],
    // pagination: curseur pour récupérer la page suivante
    "nextCursor": "eyJwYWdlIjoyfQ=="
  }
}
// Réponse en erreur (serveur → client)
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
	  // code (ex: -32602 = invalid params)
    "code": -32602, 
    // message court
    "message": "Le champ 'date' doit être au format YYYY-MM-DD",
    // data optionnels (prochain pas)
    "data": { "next": "corriger 'date' (ex: 2025-06-15) et relancer" }
  }
}
icon alerte

Important

Concernant la découverte dynamique : les clients MCP rafraîchissent généralement la liste des tools à l’ouverture de session. Prévoyez un mécanisme de rechargement/ré‑annonce après déploiement (certains clients mettent en cache la liste des outils et leurs schémas pendant quelques minutes).

Empreinte tokens et fenêtre de contexte

La « fenêtre de contexte » désigne la quantité maximale d’informations qu’un modèle peut prendre en compte à un instant donné. Elle s’exprime en tokens et inclut tous les éléments envoyés au modèle (messages système/utilisateur, résultats de tools, ressources, prompts) ainsi qu’une partie de la réponse générée. Si l’on dépasse cette capacité, le modèle doit tronquer ou oublier des éléments plus anciens. Plus on injecte de texte, moins il reste d’espace pour le raisonnement et la réponse; d’où l’importance de réponses compactes. La taille de cette fenêtre varie selon les modèles.

Sur les coûts, l’essentiel vient des contenus injectés dans cette fenêtre : résultats de tools/resources et prompts côté client. À l’inverse, les messages du protocole et le simple fait d’appeler un tool pèsent peu.

Deux repères simples aident à se situer :

  • ~1 token ≈ 3–4 caractères
  • 2 Ko de JSON ≈ ~500 tokens

Par exemple, une réponse searchFlights qui renvoie 10 options avec 5 champs courts chacun (~300 caractères au total) coûte ≈ 80–120 tokens. La même réponse avec 15 champs riches (règlement bagages, conditions tarifaires détaillées), descriptions longues et politiques imbriquées peut dépasser 2–4 Ko, soit 500–1 000 tokens, saturant vite la fenêtre si elle s’accumule.

En pratique, visez des réponses < 10–20 Ko et n’autorisez plus que via pagination ou résumé. Si l’assistant a besoin de détails, faites‑le en deux temps : renvoyer d’abord une liste compacte, puis un appel ciblé pour l’élément choisi (ex : getFlightById(id)), ou ajouter un paramètre expand=true pour ne renvoyer que les champs supplémentaires/la page suivante.

Pour rester dans une enveloppe raisonnable, privilégiez les approches suivantes :

  • Préférer des identifiants et quelques champs utiles aux objets verbeux
  • Appliquer les filtres côté serveur, imposer une pagination stricte
  • Générer des résumés (synthèses) côté serveur (titres, états, horodatages pertinents)
  • Rendre les erreurs courtes et normées (code, message concis, prochain pas)

Patrons d’exposition d’un backend via MCP

On commence souvent par la lecture. Exposer un “query tool” de type quotidien (ex : searchFlights, listCalendarEvents, findRestaurants) avec filtres, pagination et sélection de champs permet de cadrer le volume d’information tout en restant utile au LLM. Par exemple, searchFlights accepte des filtres simples (origin, destination, date) et renvoie uniquement id, airline, price, departAt, arriveAt. Si le modèle a besoin du détail, un tool séparé getFlightById fournit une vue approfondie (bagages, conditions tarifaires) paginée.

Mini‑exemple (compact vs verbeux), contenu du champ result

// Compact (champs utiles)
{
  "items": [
    { 
	    "id": "FL-1234", 
	    "airline": "Iberia", 
	    "price": "350 €", 
	    "departAt": "2025-06-15T10:10:00Z", 
	    "arriveAt": "2025-06-15T22:05:00Z" 
	   }
  ],
  "nextCursor": "eyJwYWdlIjoyfQ=="
}
// Verbeux (anti‑pattern pour défauts): politiques complètes, texte libre, champs imbriqués
{
  "items": [
    {
      "id": "FL-1234",
      "airline": "Iberia",
      "price": "350 €",
      "departAt": "2025-06-15T10:10:00Z",
      "arriveAt": "2025-06-15T22:05:00Z",
      "baggagePolicy": { /* texte long */ },
      "fareRules": "... 3 000+ caractères ...",
      "notes": [ "politique d’embarquement ...", "conditions météo ..." ]
    }
  ]
}
icon alerte

Bon à savoir

nextCursor est un jeton opaque que le client renvoie au tool pour récupérer la page suivante. Ne l’interprétez pas côté client; faites‑le expirer rapidement côté serveur.

Pour les actions, raisonnez comme sur des commandes. Par exemple, createCalendarEvent doit pouvoir être relancé sans créer de doublon : envoyez un identifiant unique de requête; si l’appel revient avec le même identifiant, le serveur n’applique pas l’action une seconde fois. Proposez aussi un “dry‑run” quand c’est critique (prévisualiser l’impact avant d’exécuter), et produisez des journaux d’audit structurés (qui, quoi, quand, avec quels paramètres).

Exemple tools/call (dry‑run + relance sans doublon)

// Appel (client → serveur)
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "createCalendarEvent",
    "arguments": {
      "requestId": "c9a1e3a0-7d6a-4f2a-8a6b-12f9e7",
      "dryRun": true,
      "title": "Barcelona Trip",
      "startDate": "2025-06-15T09:00:00Z",
      "endDate": "2025-06-22T18:00:00Z"
    }
  }
}
// Réponse dry-run (serveur → client)
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "dryRun": true,
    "impact": { "conflictsDetected": true, "conflictingEvents": ["meet-123"] },
    "canApply": true,
    "next": "relancer avec dryRun=false pour créer l’événement"
  }
}
// Réponse appliquée (même requestId => pas de doublon)
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "eventId": "EVT-987",
    "status": "created"
  }
}

Côté authentification, deux schémas couvrent l’essentiel en pratique.

  • “On‑behalf‑of” quand les droits de l’utilisateur final s’appliquent : le serveur MCP échange un jeton court et limité aux permissions nécessaires à partir d’un jeton de rafraîchissement interne, exécute l’action au nom de l’utilisateur, puis invalide/expire correctement.
  • “Compte de service” pour des tâches techniques ou globales.

Dans tous les cas, chiffrer et faire tourner les jetons de rafraîchissement (vault/KMS), ne jamais renvoyer de secrets vers le client, et réaliser le rafraîchissement côté serveur. Documentez vos scopes et mappez-les à vos rôles métiers pour éviter les élévations de privilèges implicites.

schema MCP

Côté garde‑fous, mieux vaut être explicite :

  • Tenez une liste autorisée des tools publiés
  • Fixez des limites de taille (octets, éléments) et de temps (timeouts) par tool
  • Validez strictement les schémas d’entrée
  • Renvoyez des erreurs courtes et actionnables (codes, champs manquants, limites atteintes)

Sur l’observabilité, partez simple : latence p50/p95, taille moyenne des réponses et “tokens estimés” par tool suffisent à détecter les dérives. Ajoutez des alertes quand un tool dépasse son budget de tokens ou son temps de réponse cible.

Exemple (erreur normée et brève) — (tools/call)

{  
	"jsonrpc": "2.0",  
	"id": 3,  
	"error": {    
		"code": 402,    
		"message": "Mode de paiement manquant",    
		"data": { 
			"next": "attachPaymentMethod(customerId)" 
		}  
	}
}

La voie d’implémentation dépend de votre contexte. Un serveur “maison” via le SDK donne un contrôle fin : vous exposez des tools orientés cas d’usage, avec des réponses (outputs) compactes sur‑mesure — idéal pour maîtriser les tokens, au prix d’un effort plus élevé.

Un proxy OpenAPI/GraphQL accélère le démarrage si vous avez un schéma robuste : il expose vite une large surface, mais exige de la réduction/synthèse (champs, résumés), des filtres et une pagination stricte, faute de quoi la consommation de tokens explose. Un chemin hybride fonctionne bien en entreprise : générer le proxy pour la couverture générale, puis adapter finement les 10–20 parcours critiques avec des tools dédiés et des schémas stricts.

Côté opérations, traitez chaque tool comme une capacité produit, avec budgets et objectifs. Définissez un budget de tokens par appel par défaut, et des garde‑fous (ex : refuser au‑delà de 20 Ko sans pagination). Mesurez latence, taille des réponses et tokens renvoyés; suivez les tendances.

Sur le versioning, privilégiez les évolutions additives; pour un changement rupturant, publiez un nouveau tool (ex : createCalendarEvent_v2) et laissez une fenêtre de coexistence avec dépréciation annoncée. N’oubliez pas le cache de la liste d’outils côté clients : après déploiement, forcez un rechargement ou une ré‑annonce pour éviter des incohérences.

Enfin, concevez vos tools “orientés tokens”. Évitez le mapping 1:1 de endpoints verbeux. Préférez des tools centrés sur la tâche à accomplir, qui renvoient des identifiants ou quelques champs utiles, et offrent un chemin explicite pour obtenir davantage (pagination, résumé, “expand”). Ce design réduit la charge cognitive du modèle et votre facture tout en améliorant la fiabilité des appels.

Les points de vigilance à retenir (protocole + clients)

  • Ne pas exposer toute l’API telle quelle « pour commencer » (explosion des tokens, erreurs ambiguës)
  • Ne pas laisser des champs « debug » verbeux dans les réponses par défaut
  • Ne pas renvoyer des erreurs non normées (préférer codes courts + prochaine étape)
  • Éviter les doublons sur les créations/écritures (idempotence)
  • Vérifier la compatibilité côté clients : tools/resources/prompts ne sont pas toujours tous supportés.

Ressources pour aller plus loin

Checklist rapide pour avancer

  • Choisir les premiers tools (lecture seule, outputs compacts)
  • Définir schémas d’E/S et stratégies de pagination/résumé
  • Mettre en place auth, scopes, journaux d’audit, rate limits
  • Instrumenter métriques (latence, taille, tokens) et alertes
  • Politique de versioning + dépréciation; docs internes pour les équipes

Pour conclure

MCP vous donne un cadre sobre pour ouvrir votre backend aux LLM sans sacrifier la maîtrise : outils découverts, schémas clairs, garde‑fous explicites.

En exposant d’abord des lectures compactes, en traitant les écritures comme des commandes relançables sans doublon et en imposant budgets et pagination, vous limitez les coûts tout en augmentant la fiabilité. Le choix entre serveur “maison”, proxy ou hybride dépend de votre contexte : couverture vs efficience tokens vs vitesse de mise en place. Commencez petit, mesurez, itérez.