Architecture d’un serveur MCP expliquée en schémas : de l’hôte à l’appel d’outil

Un serveur MCP se place entre une application d’IA et les systèmes qu’elle doit atteindre. Cet article dessine tout le chemin en schémas : hôte, client, serveur, transport, handshake JSON-RPC, appel d’outil, gestion des erreurs et sécurité, avec un connecteur réel d’images et de vidéos comme exemple concret.

Architecture d’un serveur MCP expliquée en schémas : de l’hôte à l’appel d’outil
Cristian Da Conceicao
Fondateur de Picasso IA

Votre assistant IA peut rédiger un contrat en quelques secondes, mais il ne peut pas consulter votre agenda, interroger votre base de données ou redimensionner une photo tout seul. Le Model Context Protocol, abrégé en MCP, comble cet écart grâce à un contrat commun entre les applications d’IA et les systèmes qui les entourent. Un serveur MCP est le petit programme situé de l’autre côté de ce contrat : il annonce ce qu’il sait faire, attend les requêtes et renvoie les résultats selon une structure prévisible. Cet article détaille cette architecture pièce par pièce. Chaque schéma est en texte brut, il se copie-colle sans problème dans un README, un document de conception ou une pull request.

Pourquoi MCP existe

Avant MCP, chaque application d’IA qui voulait accéder à une base de données, un agenda ou un système de fichiers avait besoin de son propre connecteur sur mesure. Chaque connecteur gérait sa propre authentification, son propre format d’erreur et ses propres bogues. Trois applications et trois outils faisaient déjà neuf intégrations, et cette grille grandit à chaque nouveau produit d’un côté ou de l’autre. MCP remplace la grille par un protocole commun : le calcul passe donc de applications fois outils à applications plus outils.

Main dessinant une flèche entre deux cases sur un tableau blanc brillant

Before MCP: one custom connector for every pair

 App A ──► Database    App B ──► Database    App C ──► Database
 App A ──► Calendar    App B ──► Calendar    App C ──► Calendar
 App A ──► Files       App B ──► Files       App C ──► Files

 3 apps x 3 tools = 9 connectors to build and maintain


With MCP: one shared protocol in the middle

 App A ──┐                         ┌── Database server
 App B ──┼──── MCP (JSON-RPC) ─────┼── Calendar server
 App C ──┘                         └── Files server

 3 clients + 3 servers = 6 pieces

Le mot serveur induit en erreur. Un serveur MCP n’est pas un modèle de langage et ne raisonne pas. C’est un programme ordinaire, écrit en TypeScript, en Python ou dans n’importe quel langage disposant d’une bibliothèque JSON, qui encapsule une capacité réelle et la décrit dans un format que tout client compatible peut lire. Le modèle n’a jamais besoin de savoir comment fonctionne le pilote de votre base de données. Il doit seulement savoir qu’un outil nommé run_query existe et quels arguments il accepte.

Les trois rôles en un schéma

MCP définit trois rôles, et les confondre est à l’origine de la plupart des malentendus. Voici le tableau d’ensemble avant les détails.

┌──────────── HOST (the AI application) ────────────┐
│  The LLM picks a tool, the host routes the call   │
│                                                   │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐   │
│  │  client 1  │  │  client 2  │  │  client 3  │   │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘   │
└────────┬───────────────┬───────────────┬──────────┘
         │ session       │ session       │ session
   ┌─────┴──────┐  ┌─────┴──────┐  ┌─────┴──────┐
   │  Server A  │  │  Server B  │  │  Server C  │
   │   files    │  │   GitHub   │  │ image tool │
   └────────────┘  └────────────┘  └────────────┘

L’hôte gère la conversation

L’hôte est l’application que la personne utilise réellement : une application de chat sur ordinateur, un assistant intégré à un IDE ou un agent personnalisé. Il exécute le modèle de langage, décide à quels serveurs se connecter et montre à l’utilisateur ce qui va se passer. Les modèles de raisonnement comme Claude Sonnet 5, GPT 5.6 Sol et Gemini 3.1 Pro fonctionnent à l’intérieur de l’hôte, mais ils ne parlent jamais MCP eux-mêmes. L’hôte fait la traduction entre le format d’appel d’outils du modèle et le protocole, ce qui explique qu’un même serveur fonctionne avec de nombreux modèles différents.

Le client tient une session

Dans l’hôte, un client MCP est créé pour chaque serveur. Chaque client maintient une session avec état, en relation un à un avec un seul serveur, il mémorise les capacités convenues par les deux parties et achemine chaque message. Un hôte connecté à trois serveurs exécute donc trois clients, comme le montre le schéma. Si une session est interrompue, les deux autres continuent de fonctionner.

Le serveur fait le travail

Le serveur MCP encapsule un système réel : une base Postgres, un compte GitHub, un dossier de documents ou une API d’images. Il reste volontairement petit. Il déclare ce qu’il propose, valide les entrées, exécute l’action et renvoie une sortie structurée. Il ne voit jamais la conversation complète, seulement les requêtes qui lui sont adressées, un choix de conception qui protège la confidentialité et rend les serveurs réutilisables.

Schémas d’architecture imprimés étalés sur un bureau en noyer avec un ordinateur portable et des notes adhésives

Un récapitulatif rapide à épingler au-dessus de votre bureau :

  • Hôte : possède le modèle, l’interface utilisateur et les demandes de consentement.
  • Client : un par serveur, parle le protocole et conserve l’état de la session.
  • Serveur : expose des capacités, exécute l’action et renvoie les résultats.

Ce qu’expose un serveur

Un serveur propose trois briques de base, appelées primitives. Elles diffèrent sur un point qui façonne toute la conception : qui décide du moment où chacune est utilisée.

PrimitiveContrôlée parUsage typiqueExemples de méthodes
OutilsLe modèleExécuter une action ou calculer un résultattools/list, tools/call
RessourcesL’applicationFournir un contexte en lecture seule, comme des fichiers ou des enregistrementsresources/list, resources/read
PromptsL’utilisateurModèles réutilisables, souvent affichés sous forme de commandes slashprompts/list, prompts/get

Main écrivant un tableau à trois colonnes bien net dans un carnet pointillé à côté d’une règle en acier

Les outils exécutent des actions

Un outil possède un nom, une description en langage courant et un inputSchema écrit en JSON Schema. Le modèle lit la description pour décider si l’outil convient à la demande, et le schéma garantit la validité des arguments. Les descriptions méritent un vrai effort, car une description vague oblige le modèle à deviner. Nommez les outils comme des verbes, gardez chacun étroit et ne renvoyez que les champs dont le modèle a besoin. Un outil nommé search_orders avec trois arguments typés l’emporte sur un seul outil do_anything avec un champ de texte libre. Les résultats d’outils ne se limitent pas au texte : ils peuvent contenir des images, de l’audio ou des liens, ce qui explique pourquoi les générateurs de médias s’intègrent si naturellement au protocole. Un outil d’image peut encapsuler Flux 2 Pro ou Seedream 4.5, et un outil vidéo peut encapsuler Veo 3.1 ou Kling v3 Video.

Les ressources fournissent du contexte

Les ressources sont des données en lecture seule, adressées par URI, comme file:///reports/q3.md ou postgres://db/customers/schema. L’hôte choisit lesquelles joindre au contexte du modèle, ce qui convient aux documents, aux schémas et aux journaux. Une fois qu’un client s’est abonné, un serveur peut signaler les changements avec notifications/resources/updated.

Les prompts proposent des modèles

Les prompts sont des modèles de messages paramétrables que l’utilisateur choisit volontairement, généralement depuis un menu de commandes slash. Un prompt tel que examine cette pull request renvoie une liste de messages prête à l’emploi, afin que chaque membre de l’équipe parte de la même formulation et de la même checklist.

Les échanges vont aussi dans l’autre sens. Les serveurs peuvent demander de l’aide au client grâce au sampling (demander une complétion au modèle de l’hôte), aux roots (demander quels répertoires sont dans le périmètre) et à l’elicitation (demander à l’utilisateur une information manquante). L’hôte décide d’autoriser ou non chacun d’eux.

💡 Règle empirique : si l’action modifie quelque chose dans le monde, créez un outil. Si elle ne fournit que de l’information, commencez par une ressource.

Transports : stdio ou Streamable HTTP

Le transport détermine la façon dont les octets circulent entre le client et le serveur. Les messages restent identiques dans les deux cas : requêtes, réponses et notifications JSON-RPC 2.0. Deux transports sont standard.

stdio pour les serveurs locaux

stdio: the host starts the server as a child process

 ┌────────┐  stdin: requests       ┌───────────┐
 │ Client │ ─────────────────────► │  Server   │
 │        │ ◄───────────────────── │  process  │
 └────────┘  stdout: responses     └───────────┘
                                   stderr: logs only

Avec stdio, l’hôte lance le serveur comme processus enfant et échange des messages JSON-RPC délimités par des retours à la ligne sur l’entrée et la sortie standard. La configuration tient en une ligne de commande, la latence est minime et les identifiants arrivent par des variables d’environnement. Une règle fait trébucher beaucoup d’auteurs débutants : le serveur ne doit jamais afficher autre chose que des messages de protocole sur stdout. Les journaux vont vers stderr, sinon le flux se corrompt et la session s’arrête.

Câble Ethernet inséré dans un port de commutateur réseau avec des voyants d’état verts

Streamable HTTP pour les serveurs distants

Streamable HTTP: one URL, many clients

 ┌──────────┐  POST /mcp           ┌────────────┐
 │ Client A │ ───────────────────► │            │
 └──────────┘ ◄─────────────────── │   Server   │
 ┌──────────┐  JSON or SSE reply   │  (web app) │
 │ Client B │ ───────────────────► │            │
 └──────────┘ ◄─────────────────── └────────────┘

Streamable HTTP dessert de nombreux clients depuis un seul point de terminaison. Le client envoie chaque message en requête HTTP POST, et le serveur répond soit en JSON simple, soit en ouvrant un flux Server-Sent Events lorsqu’il doit envoyer plusieurs messages. Un identifiant de session circule dans l’en-tête Mcp-Session-Id. Ce transport a remplacé l’ancienne conception HTTP plus SSE dans la révision 2025-03-26 de la spécification, et c’est le bon choix pour les serveurs hébergés, les produits multi-utilisateurs et tout ce qui se trouve derrière un répartiteur de charge. L’autorisation repose sur OAuth : un serveur peut donc répondre 401 et indiquer au client son serveur d’autorisation. Les serveurs qui utilisent encore l’ancienne conception peuvent rester compatibles en exposant les deux points de terminaison pendant une migration, mais un nouveau projet doit démarrer sur Streamable HTTP.

QuestionstdioStreamable HTTP
Où le serveur s’exécute-t-il ?Sur la même machine que l’hôtePartout où une URL est accessible
Utilisateurs par serveurUnPlusieurs
IdentifiantsVariables d’environnementOAuth ou en-têtes HTTP
Idéal pourOutils de développement, fichiers locauxProduits hébergés, services partagés
Principal piègeSorties parasites sur stdoutGestion des sessions derrière les proxys

Vue en contre-plongée d’une allée de centre de données entre des armoires serveurs noires

Une répartition pratique : livrez une version stdio aux développeurs qui veulent essayer le serveur en une minute, et une version Streamable HTTP à tous les autres. Le code des outils reste le même. Seul le point d’entrée change.

Un appel d’outil, pas à pas

Voici une requête unique, depuis le moment où l’utilisateur tape jusqu’à l’affichage de la réponse.

 User              Host + LLM          MCP client          MCP server
   │                    │                   │                   │
   ├─ asks for image ───►                   │                   │
   │                    │ picks a tool      │                   │
   │                    ├─ tool request ────►                   │
   │                    │                   ├─ tools/call ──────►
   │                    │                   │                   │ does the work
   │                    │                   ◄─ text, isError ───┤
   │                    ◄─ result ──────────┤                   │
   ◄─ answer + URL ─────┤                   │                   │
   │                    │                   │                   │

Étape 1 : la poignée de main

Chaque session commence par initialize. Le client envoie la version du protocole qu’il prend en charge, ainsi que ses propres capacités. Le serveur répond avec la version qu’il a retenue et les capacités qu’il propose. Le client envoie ensuite un message notifications/initialized et le trafic normal commence. Si les versions ne peuvent pas être conciliées, le client se déconnecte au lieu de deviner.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "sampling": {} },
    "clientInfo": { "name": "example-host", "version": "1.0.0" }
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": true } },
    "serverInfo": { "name": "image-server", "version": "0.3.0" }
  }
}

Deux ingénieurs logiciels relisant du code debout à leur bureau

Étape 2 : lister et appeler

Le client envoie tools/list, l’hôte transmet les schémas au modèle, et le modèle décide d’en appeler un. Lorsque la liste des outils d’un serveur change à l’exécution, il envoie notifications/tools/list_changed pour que le client la rafraîchisse. Un appel ressemble à ceci :

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "generate_image",
    "arguments": { "prompt": "A walnut desk with printed diagrams", "aspect_ratio": "16:9" }
  }
}

La réponse contient un tableau content et un indicateur isError :

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [{ "type": "text", "text": "Job accepted, check status in 5 seconds" }],
    "isError": false
  }
}

Étape 3 : quand les appels échouent

MCP distingue deux types d’échec. Une erreur de protocole est un objet d’erreur JSON-RPC, par exemple le code -32602 pour des paramètres invalides ou un nom d’outil inconnu. Une erreur d’exécution d’outil est un résultat normal accompagné de isError: true et d’un message que le modèle peut lire. Le second type compte le plus. Lorsqu’un outil répond prompt too long, le modèle peut raccourcir le prompt et réessayer, mais seulement si l’erreur lui parvient sous forme de texte et non sous forme de session plantée. Chaque partie peut aussi envoyer notifications/cancelled pour abandonner une requête lente.

Développeur devant un bureau en bois sombre au crépuscule, lisant les journaux sur deux écrans

Lancez le serveur sous le MCP Inspector (npx @modelcontextprotocol/inspector) avant que tout modèle ne l’utilise. L’Inspector liste les outils, vous permet de lancer des appels à la main et affiche le trafic JSON-RPC brut, ce qui permet de séparer les bogues de protocole des bogues de prompt.

3 erreurs de conception courantes

La plupart des problèmes en production proviennent des mêmes trois choix :

  • Un outil géant. Un outil nommé do_anything avec un argument en texte libre oblige le modèle à deviner. Découpez-le en verbes précis comme search_orders et refund_order, chacun avec des arguments typés.
  • Des résultats bavards. Renvoyer un bloc de 40 000 tokens consomme la fenêtre de contexte du modèle. Renvoyez les champs dont le modèle a besoin et ajoutez un lien vers une ressource pour le reste.
  • État caché. Si un outil ne fonctionne qu’après l’exécution d’un autre outil, indiquez-le dans sa description, sinon le modèle les appellera dans le mauvais ordre.

Un vrai serveur : outils d’image et de vidéo

Un cas concret montre pourquoi les choix d’architecture comptent. PicassoIA propose un connecteur MCP dont les outils génèrent et modifient des images et des vidéos sur ses propres GPU. Les tâches d’image et de vidéo prennent de quelques secondes à quelques minutes, ce qui casse l’image naïve de appeler un outil, attendre la réponse. Les hôtes et les SDK imposent généralement un délai d’expiration des requêtes, donc un serveur qui bloquerait jusqu’à la fin du rendu échouerait précisément au moment où le travail est presque terminé.

Des tâches asynchrones derrière un outil simple

Le connecteur expose des outils nommés generate_image, edit_image, generate_video_picassoia, generate_video_seedance, get_generation, cancel_generation, list_models, list_generations et get_account. Un appel de génération renvoie immédiatement un identifiant de prédiction et une durée estimée. Le modèle appelle ensuite get_generation après le délai suggéré, puis à nouveau après chaque nouvelle indication, jusqu’à ce que le statut indique succeeded ou failed. L’appel d’outil reste court pendant que le travail lourd s’exécute en tâche de fond sur un worker GPU.

 Model               MCP server              GPU worker
   │                      │                       │
   ├─ generate_image ─────►                       │
   │                      ├─ submit job ──────────►
   ◄─ id + wait hint ─────┤                       │
   │                      │                       │ rendering
   ├─ get_generation ─────►                       │
   ◄─ status: processing ─┤                       │
   ├─ get_generation ─────►                       │
   ◄─ succeeded + URL ────┤                       │
   │                      │                       │

Les modèles derrière le connecteur sont PicassoIA Image et PicassoIA Image Editor Pro pour les images fixes, ainsi que PicassoIA Video et Seedance 2.5 Lite pour les clips avec audio. Les résultats reviennent sous forme d’URL simples, que n’importe quel hôte peut afficher.

Local réseau bien rangé avec une baie murale et un ingénieur qui vérifie un câble

Pourquoi les limites de concurrence comptent

Un serveur qui se place devant une file d’attente GPU doit la protéger. PicassoIA indique un plafond de cinq prédictions simultanées par compte, partagé entre les identifiants API et les connexions MCP. Un serveur bien conçu transforme un plafond de ce type en résultat d’outil clair, par exemple cinq tâches en cours, réessayez dans 30 secondes, au lieu de laisser les requêtes s’empiler derrière. Le modèle peut lire ce message et patienter, ce qui vaut bien mieux qu’un délai d’expiration.

Quatre bonnes pratiques rendent un serveur asynchrone agréable à utiliser depuis un agent :

  • Répondre vite. Renvoyez un identifiant en moins d’une seconde et ne bloquez jamais pendant des minutes.
  • Indiquer l’attente. Dites au modèle quand interroger à nouveau le statut pour qu’il n’inonde pas l’outil de statut de requêtes.
  • Rendre l’échec définitif. Une tâche échouée reste échouée, et le message explique pourquoi.
  • Proposer un outil d’annulation. Les utilisateurs changent d’avis, et un travail en file d’attente coûte de l’argent.

Où placer la sécurité

Lourd cadenas en acier sur une chaîne sécurisant la porte d’une cage de serveur en treillis métallique

MCP transfère des capacités, donc il transfère aussi des risques. Le protocole fixe la forme de la conversation, mais l’hôte et le serveur portent la responsabilité. Six contrôles permettent d’attraper la plupart des problèmes avant la mise en production :

  • Consentement de l’utilisateur. L’hôte doit indiquer quel outil est sur le point de s’exécuter et demander l’accord de l’utilisateur avant toute action ayant des effets de bord.
  • Moindre privilège. Donnez à un outil en lecture seule un identifiant en lecture seule, et gardez les actions puissantes dans un serveur distinct.
  • Validation des entrées. Traitez chaque argument comme non fiable. Validez aussi selon le schéma côté serveur, pas seulement dans le client.
  • Injection de prompt. Le texte contenu dans un résultat d’outil ou dans une ressource peut contenir des instructions. L’hôte doit le traiter comme des données, jamais comme une commande de l’utilisateur.
  • Gestion des secrets. Ne placez jamais d’identifiants dans les descriptions ou les résultats des outils. Les serveurs stdio les lisent depuis l’environnement, et les serveurs HTTP utilisent OAuth.
  • Journaux d’audit. Écrivez des journaux structurés avec un identifiant de requête vers stderr ou vers un service de journalisation, afin que chaque appel d’outil puisse être tracé après coup.

Le déploiement suit la même logique. Figez la version du protocole dans vos tests, lancez le serveur sous l’Inspector en CI et placez les serveurs distants derrière une passerelle qui gère le TLS, les limites de débit et OAuth, afin que le code des outils reste concentré sur les outils.

Essayez vous-même la génération d’images et de vidéos

Les schémas se retiennent mieux quand vous voyez un appel d’outil produire quelque chose de concret. Ouvrez PicassoIA, écrivez un prompt pour une photo de votre bureau, d’une salle serveur ou d’une esquisse sur tableau blanc, et générez-la avec Seedream 4.5 ou GPT Image 2. Animez ensuite votre image préférée avec Veo 3.1 ou Kling v3 Video. Si votre hôte prend en charge les connexions MCP, ajoutez le connecteur PicassoIA et laissez votre assistant lancer la tâche pendant que vous suivez les étapes d’interrogation du schéma ci-dessus.

Essayez trois prompts en changeant une seule chose à chaque fois : l’angle de caméra, la direction de l’éclairage ou l’objectif. Les différences montrent à quel point un prompt précis compte, de la même façon qu’une description d’outil précise compte pour un modèle. Parcourez tous les modèles disponibles sur picassoia.com/en/all-models et lancez votre première génération dès aujourd’hui.

Partager cet article

Choisissez votre langue