Quand un assistant IA a besoin de lire un fichier, d’interroger une base, d’envoyer un mail ou d’exécuter une commande système, il lui faut un canal standardisé pour parler au monde extérieur. Le Model Context Protocol (MCP) est ce canal. Pensé par Anthropic en novembre 2024 puis transféré en décembre 2025 à l’Agentic AI Foundation hébergée par la Linux Foundation, MCP est devenu en moins de dix-huit mois la couche d’intégration de référence entre les LLM et tout ce qui n’est pas un LLM. Construire un serveur MCP, c’est exposer une capacité — vos données, vos APIs, vos workflows — sous une forme qu’un agent pourra découvrir, négocier et invoquer sans que vous ayez à réécrire le moindre adaptateur pour chaque assistant.
Ce guide pose toute l’architecture nécessaire pour passer du « je connais MCP de nom » à « j’ai un serveur en production qui répond à un agent ». On part de la spécification officielle du 25 novembre 2025, on décortique les trois primitives serveur (tools, resources, prompts), on choisit un transport, on écrit un premier serveur en Python et en TypeScript, on le branche à un client réel (Claude Desktop, VS Code, Cursor), et on prépare le terrain pour les trois mises en pratique qui suivent : intégration Next.js, branchement à PostgreSQL, durcissement OAuth 2.1.
Sommaire
- MCP en une phrase, et pourquoi ce protocole a gagné
- L’architecture host / client / server
- Les trois primitives serveur : tools, resources, prompts
- Les trois primitives client : sampling, roots, elicitation
- Le handshake JSON-RPC et la négociation de capacités
- Les transports : stdio, Streamable HTTP, SSE legacy
- Choisir son SDK : Python, TypeScript, C#, Java, Go
- Premier serveur en Python avec FastMCP
- Premier serveur en TypeScript
- Brancher le serveur à un client
- Erreurs fréquentes au démarrage
- Les trois mises en pratique du parcours
- FAQ
MCP en une phrase, et pourquoi ce protocole a gagné
Model Context Protocol est un protocole ouvert qui standardise la manière dont une application LLM échange du contexte et appelle des outils externes. Là où chaque éditeur d’IDE ou de chatbot inventait jusqu’en 2024 son propre système de plugins (la « function calling » d’OpenAI, les « tools » d’Anthropic, les extensions VS Code, les plugins Cursor), MCP propose un seul format de messages, une seule manière de découvrir ce qu’un serveur sait faire, et un seul jeu d’opérations pour l’invoquer. Le résultat est immédiat : un serveur écrit pour Claude Desktop fonctionne tel quel dans VS Code, dans Cursor, dans Zed, dans n’importe quel client compatible.
L’analogie qui revient le plus souvent dans la documentation officielle est celle du Language Server Protocol. LSP a fait au monde des éditeurs ce que MCP fait au monde des assistants : un serveur unique, des dizaines de clients. Concrètement, si vous écrivez un serveur MCP qui expose vos tickets Jira, vos commits Git ou votre catalogue produit, vous le rendez accessible à tout l’écosystème agentique en une seule passe. C’est pour cette raison qu’en moins d’un an le registre public de serveurs communautaires a dépassé plusieurs milliers d’implémentations et que la version 2025-11-25 de la spécification a formalisé l’autorisation OAuth 2.1, signe que le protocole entre dans sa phase « production en entreprise ».
L’architecture host / client / server
Le vocabulaire MCP est précis et il vaut la peine d’être appris dès la première heure, sous peine de confondre les rôles dans le code. Trois acteurs entrent en scène. Le host est l’application LLM qui initie les connexions : Claude Desktop, l’extension Copilot dans VS Code, l’éditeur Cursor, votre propre chatbot maison. Le host orchestre les sessions, gère le consentement utilisateur et décide à quel moment un outil peut être invoqué. Le client est le composant interne du host qui parle JSON-RPC à un serveur donné — un host peut maintenir plusieurs clients en parallèle, un par serveur. Le server est votre code : un processus séparé qui expose des outils, des ressources et des prompts, et qui répond aux requêtes du client.
Cette séparation n’est pas cosmétique. Elle conditionne directement la sécurité du système : le host est le seul à voir les données sensibles de l’utilisateur, le client n’expose au serveur que ce que l’utilisateur a explicitement consenti à partager, et le serveur n’a aucun accès direct au LLM sauf si le client lui ouvre un canal de sampling. Quand vous lirez plus loin pourquoi la spécification insiste autant sur le consentement explicite avant chaque invocation d’outil, gardez ce schéma en tête : le serveur est par défaut une boîte noire potentiellement hostile, c’est l’utilisateur qui l’autorise à agir, pas le LLM.
Les trois primitives serveur : tools, resources, prompts
Un serveur MCP peut exposer trois types d’objets, et la distinction entre les trois est cruciale pour bien concevoir son API. Les tools sont des fonctions que le LLM peut appeler de sa propre initiative : « envoie un mail », « lance ce build », « interroge la base ». Chaque outil à un nom, une description en langage naturel destinée au LLM, un schéma JSON pour ses paramètres, et un handler côté serveur qui exécute la logique. Les outils sont la primitive la plus utilisée — c’est par eux qu’on commence presque toujours.
Les resources sont des données nommées que l’utilisateur ou le LLM peut lire en contexte : un fichier, un enregistrement de base, une image, le contenu d’une page interne. Une resource est identifiée par une URI (par exemple file:///rapports/2026-Q1.md ou postgres://commandes/table/clients) et le serveur sait la matérialiser à la demande. La différence avec un outil ? Une resource est passive : on la lit, elle ne déclenche pas d’effet de bord. Un outil est actif : il agit. Cette distinction guide vos clients sur la posture de sécurité à adopter — lire un fichier déjà partagé est moins risqué qu’exécuter une commande arbitraire.
Les prompts sont des templates de messages que le serveur propose à l’utilisateur via le client. Pensez-y comme des « actions rapides » contextuelles : un serveur Git pourrait exposer un prompt « rédige le message de commit pour ces changements », un serveur de support client pourrait offrir « génère une réponse polie à ce ticket en respectant notre charte ». Les prompts sont la primitive la moins utilisée des trois mais aussi la plus puissante pour packager du savoir-faire métier : c’est là que vous transformez une simple intégration de données en assistant véritablement spécialisé.
Les trois primitives client : sampling, roots, elicitation
Symétriquement, le client peut offrir trois capacités au serveur, et c’est ici que MCP devient véritablement bidirectionnel. Le sampling permet au serveur de demander au client d’exécuter une requête LLM en son nom — c’est ce qui rend possibles les workflows agentiques récursifs où un serveur d’orchestration appelle d’autres serveurs en s’appuyant sur le LLM pour décider du plan. Le sampling est puissant et c’est précisément pour cela que la spécification exige un consentement utilisateur explicite à chaque appel : sans ce garde-fou, un serveur compromis pourrait épuiser le quota du LLM ou exfiltrer du contexte.
Les roots sont une manière pour le client de déclarer au serveur quelles URIs il a le droit de toucher. Un serveur de système de fichiers négociera par exemple un root sur /home/alice/projets et saura qu’il ne peut pas lire ailleurs. L’elicitation, enfin, ajoutée dans la révision 2025-06 de la spécification, permet au serveur de demander une information complémentaire à l’utilisateur via le client — par exemple « quelle branche dois-je pousser ? » avant d’exécuter un push potentiellement destructif. Cette dernière capacité est encore peu implémentée par les clients mais elle change la nature des serveurs qu’on peut écrire : on passe de « fonctions à un coup » à « workflows interactifs ».
Le handshake JSON-RPC et la négociation de capacités
Sous le capot, MCP est un protocole JSON-RPC 2.0 stateful. Chaque message est un objet JSON conforme à la spécification JSON-RPC, avec un id, une method et des params. La conversation commence toujours par une phase d’initialize où le client envoie sa version du protocole et la liste de ses capabilities, et où le serveur répond avec sa propre version et ses propres capabilities. Cette négociation détermine ce qui est autorisé pour le reste de la session : si le serveur déclare ne pas supporter les prompts, le client n’enverra jamais de prompts/list.
Voici à quoi ressemble un handshake minimal au format brut, capturé sur stdio :
// Client → Serveur
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-11-25",
"capabilities":{"sampling":{},"roots":{"listChanged":true}},
"clientInfo":{"name":"my-client","version":"1.0.0"}
}}
// Serveur → Client
{"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2025-11-25",
"capabilities":{"tools":{"listChanged":true},"resources":{}},
"serverInfo":{"name":"my-server","version":"0.1.0"}
}}
// Client → Serveur (notification, pas de réponse attendue)
{"jsonrpc":"2.0","method":"notifications/initialized"}
Ce que vous voyez ici suffit à comprendre 90 % des bugs MCP en production : si la version du protocole annoncée par le client ne correspond pas à ce que le serveur supporte, la session échoue avant même la première invocation d’outil. Si le serveur déclare une capability qu’il n’implémente pas réellement, le client va appeler une méthode qui retournera une erreur JSON-RPC. Logger ces deux événements dès le premier jour vous économisera des heures de débogage à l’aveugle.
Les transports : stdio, Streamable HTTP, SSE legacy
MCP définit deux transports principaux et un troisième en voie de dépréciation. Le stdio est le plus simple : le serveur est un processus enfant lancé par le host, qui lit les messages JSON-RPC sur sa stdin et écrit ses réponses sur sa stdout. Toutes les sorties d’erreur passent par stderr pour ne pas polluer le canal. C’est le transport recommandé pour les serveurs locaux : pas de port à ouvrir, pas de TLS à gérer, l’isolation est garantie par le système d’exploitation. Quand un utilisateur installe un serveur MCP dans Claude Desktop ou dans VS Code, c’est presque toujours stdio qui est utilisé.
Le Streamable HTTP, devenu la voie standard depuis la révision de mars 2025, est conçu pour les serveurs distants. Le client envoie ses messages JSON-RPC en POST sur une seule URL et le serveur peut soit répondre en JSON immédiat, soit ouvrir un canal Server-Sent Events pour pousser des messages au fil de l’eau (utile quand un outil long publie des progressions). Une session est identifiée par l’en-tête Mcp-Session-Id que le serveur émet à l’initialisation. Pour vos serveurs en production qui doivent être consultables par plusieurs équipes, par des CI ou par des agents tournant dans le cloud, c’est ce transport qu’il faut choisir.
Le SSE pur, qui était le mode HTTP d’origine, reste supporté pour la rétrocompatibilité avec les premiers clients mais n’est plus recommandé pour les nouveaux serveurs. Quand vous lirez du code communautaire qui mélange SSEServerTransport et StreamableHTTPServerTransport, sachez qu’il s’agit d’une couche d’adaptation : le second a vocation à remplacer le premier. Une variante stateless HTTP est en revue dans la spécification — elle permettra à terme de scaler horizontalement les serveurs derrière un load balancer sans coller une session à un nœud.
Choisir son SDK : Python, TypeScript, C#, Java, Go
Anthropic et la communauté maintiennent des SDKs officiels pour les principaux langages. Le SDK Python intègre depuis fin 2024 la couche FastMCP, créée par Jeremiah Lowin (Prefect) et intégrée au SDK officiel, qui rend l’écriture d’un serveur aussi concise qu’un script Flask : un décorateur @mcp.tool(), une signature de fonction typée, et c’est tout — la génération du schéma JSON, la validation des paramètres et la gestion du transport sont automatiques. C’est le SDK que je recommande aux équipes data, aux pipelines ML et à tous ceux qui ont déjà du code Python en production.
Le SDK TypeScript est celui qui à la plus grande surface dans l’écosystème web : c’est lui qui sert de socle à Vercel pour son adaptateur Next.js (mcp-handler), à Cloudflare Workers, à toutes les passerelles Node. Pour un serveur HTTP qui doit cohabiter avec une API existante, c’est un choix naturel. Les SDK C# (intégré à ASP.NET Core), Java, Kotlin et Go existent aussi et sont production-ready ; choisissez le langage de votre stack plutôt que de bâtir un pont vers un autre runtime.
Premier serveur en Python avec FastMCP
Pour mettre les mains dans le code, écrivons un serveur minimal en Python. Le but : exposer un outil convert_temperature qui convertit des Celsius en Fahrenheit, le tout en moins de quinze lignes. Avant la première commande, créez un environnement virtuel et installez le SDK officiel ; le paquet mcp[cli] embarque l’outillage de développement (commande mcp dev qui lance l’inspecteur web pour tester votre serveur sans configurer de client).
python -m venv .venv
source .venv/bin/activate # sous Windows : .venv\Scripts\activate
pip install "mcp[cli]"
L’environnement virtuel évite que mcp rentre en conflit avec d’autres versions installées globalement, et la cible [cli] ajoute la commande mcp dev dont nous nous servirons dans deux minutes. Si la commande pip install échoue avec une erreur SSL sur un réseau d’entreprise, configurez le proxy via les variables HTTPS_PROXY et HTTP_PROXY avant de relancer.
Le serveur lui-même tient en quelques lignes. Créez un fichier server.py à la racine du projet et collez le code suivant. La signature typée de la fonction sert au SDK pour générer automatiquement le schéma JSON des paramètres ; la docstring devient la description que le LLM verra quand il décidera d’appeler l’outil.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Convertisseur")
@mcp.tool()
def convert_temperature(celsius: float) -> dict:
"""Convertit une température exprimée en Celsius vers Fahrenheit."""
fahrenheit = celsius * 9 / 5 + 32
return {"celsius": celsius, "fahrenheit": round(fahrenheit, 2)}
if __name__ == "__main__":
mcp.run()
Quand vous lancez python server.py, vous ne voyez rien : c’est normal, le serveur attend des messages JSON-RPC sur sa stdin. Pour le tester de manière interactive sans configurer Claude Desktop, lancez plutôt mcp dev server.py. Une page web s’ouvre, l’inspecteur officiel découvre votre outil, et vous pouvez l’invoquer avec un formulaire. Vous devriez voir apparaître convert_temperature dans la liste, lui passer la valeur 100, et recevoir {"celsius": 100.0, "fahrenheit": 212.0}. Si l’inspecteur affiche « failed to connect », vérifiez que vous êtes bien dans le même environnement virtuel que celui où vous avez installé mcp[cli].
Premier serveur en TypeScript
Côté Node, l’équivalent passe par le SDK officiel TypeScript. Installation classique en deux commandes, pour un projet vide :
mkdir mcp-converter && cd mcp-converter
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init
Le paquet @modelcontextprotocol/sdk est l’unique dépendance à connaître côté MCP. zod sert à décrire les schémas d’entrée des outils — le SDK les transforme automatiquement en JSON Schema pour le client. tsx est un runner qui exécute du TypeScript sans étape de build, parfait pour le développement.
Créez ensuite src/server.ts avec le code ci-dessous. Notez que les imports se font sur des sous-chemins explicites du paquet SDK — c’est une habitude à prendre, ce package n’expose pas de point d’entrée monolithique pour garder le bundle final léger.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "convertisseur",
version: "0.1.0",
});
server.registerTool(
"convert_temperature",
{
title: "Conversion C -> F",
description: "Convertit une température Celsius en Fahrenheit",
inputSchema: { celsius: z.number() },
},
async ({ celsius }) => ({
content: [{
type: "text",
text: `${celsius}°C = ${(celsius * 9 / 5 + 32).toFixed(2)}°F`,
}],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);
Lancez avec npx tsx src/server.ts et le serveur attend sur stdio. Pour le tester avec l’inspecteur, l’équipe MCP fournit aussi un npx @modelcontextprotocol/inspector npx tsx src/server.ts qui ouvre la même interface web que côté Python. Cette équivalence entre les deux SDKs est volontaire : peu importe le langage, l’expérience de développement reste cohérente.
Brancher le serveur à un client
Une fois le serveur écrit, il faut qu’un host le découvre. Pour Claude Desktop, l’enregistrement se fait dans le fichier claude_desktop_config.json — sur macOS dans ~/Library/Application Support/Claude/, sur Windows dans %APPDATA%\Claude\. Pour notre serveur Python, l’entrée minimale ressemble à ceci :
{
"mcpServers": {
"convertisseur": {
"command": "python",
"args": ["/chemin/absolu/vers/server.py"]
}
}
}
Après modification, redémarrez complètement Claude Desktop (un simple « refresh » ne suffit pas : la config n’est lue qu’au démarrage). Si tout va bien, vous verrez apparaître l’icône en forme de prise dans le coin du chat, et un clic dessus listera vos outils. Pour VS Code et l’extension MCP, la procédure passe par mcp.json dans le workspace ou par la palette de commandes (MCP: Add server...). Pour Cursor, c’est dans les paramètres MCP du panneau de configuration.
La règle d’or de l’enregistrement local : utilisez toujours des chemins absolus dans la config, jamais relatifs. Le host lance le serveur depuis un répertoire de travail qui n’est pas forcément celui que vous croyez, et un ./server.py qui marche dans votre terminal échouera silencieusement quand le host le démarre. Pareil pour les variables d’environnement : exportez-les explicitement dans le bloc env de la config, sinon votre serveur ne les verra pas.
Erreurs fréquentes au démarrage
Le piège le plus classique consiste à écrire sur la stdout du serveur stdio en dehors du protocole — un print() de débogage suffit à corrompre le canal et à faire échouer toute la session. La règle est stricte : sur stdio, la stdout est réservée à JSON-RPC, tout le reste passe par stderr. En Python, configurez le logging dès le démarrage avec logging.basicConfig(stream=sys.stderr) ; en TypeScript, utilisez console.error() au lieu de console.log().
Deuxième écueil : oublier de gérer les exceptions dans les handlers d’outils. Une exception non capturée n’est pas reportée comme une erreur JSON-RPC propre — selon la version du SDK, elle peut soit faire crasher le serveur, soit produire une réponse mal formée que le client n’arrive pas à parser. Enveloppez systématiquement votre logique métier dans un try/except et retournez une réponse structurée avec un message d’erreur lisible par le LLM. Le LLM saura alors expliquer le problème à l’utilisateur ou tenter une correction.
Troisième piège, spécifique au mode HTTP : si vous oubliez l’en-tête Mcp-Session-Id côté client, le serveur refusera toutes les requêtes après l’initialisation avec un 400. Le SDK gère ça pour vous quand vous utilisez les helpers officiels, mais quand vous faites du proxying ou du test manuel à curl, c’est une cause classique de session perdue.
Les trois mises en pratique du parcours
Maintenant que les fondations sont posées, le reste du parcours rentre dans des cas concrets. Trois tutoriels approfondissent chacun un axe critique pour passer du serveur de démonstration au serveur de production.
- Déployer un serveur MCP sur Next.js (Vercel) : tutoriel pas-à-pas — exposer un endpoint MCP HTTP depuis un projet App Router existant, avec l’adaptateur officiel
mcp-handlerde Vercel, et le pousser sur un déploiement gratuit. - Brancher un serveur MCP sur PostgreSQL : tutoriel pas-à-pas — modéliser une base, écrire des outils MCP qui requêtent en lecture et en écriture, gérer les pools de connexions et la pagination.
- Sécuriser un serveur MCP avec OAuth 2.1 : tutoriel pas-à-pas — implémenter le flux OAuth 2.1 complet avec PKCE, validation d’audience, scopes et token introspection, en s’appuyant sur Keycloak en local.
Erreurs fréquentes à éviter
| Erreur | Cause | Solution |
|---|---|---|
| Sortie corrompue en stdio | print() ou console.log() dans le handler | Logger sur stderr exclusivement |
| Outil invisible côté client | Capability tools non déclarée ou serveur non redémarré | Vérifier la réponse initialize et redémarrer le host |
| Session HTTP perdue | En-tête Mcp-Session-Id non propagé | Utiliser les helpers officiels du SDK |
| Schéma JSON incorrect | Type Python ou Zod mal aligné avec ce que renvoie le handler | Aligner la signature de la fonction sur le retour réel |
| Token rejeté en HTTP | Audience (aud) absente ou ne correspondant pas à l’URL du serveur | Configurer le mapping audience dans l’autorisation server |
FAQ
Faut-il payer pour publier un serveur MCP ?
Non. Le protocole est ouvert, les SDKs sont sous licence MIT, le registre public est gratuit. Le seul coût éventuel est celui de l’hébergement quand vous passez en HTTP distant — ce qui revient au même prix qu’une API REST classique.
MCP fonctionne-t-il avec autre chose que Claude ?
Oui. À mai 2026, MCP est supporté nativement par Claude (Anthropic), ChatGPT (OpenAI), VS Code Copilot, Cursor, Zed, Continue.dev et plusieurs dizaines d’autres clients. C’est précisément la raison de son succès : un serveur écrit une fois fonctionne partout.
Quelle différence avec la « function calling » d’OpenAI ou les « tools » d’Anthropic ?
Function calling et tool use sont des conventions internes à chaque modèle : elles décrivent comment passer une liste de fonctions au LLM dans un appel d’API. MCP est un cran au-dessus : c’est le protocole par lequel un serveur extérieur expose ses fonctions à n’importe quel client, indépendamment du modèle utilisé en aval. Les deux mécanismes coexistent — quand un host appelle un outil MCP, il le traduit en interne en function call pour le modèle qu’il utilise.
Stdio ou HTTP : que choisir pour démarrer ?
Stdio pour tout serveur destiné à tourner sur la machine de l’utilisateur. Streamable HTTP dès qu’il faut partager le serveur entre plusieurs personnes ou l’héberger dans le cloud. Évitez SSE pur pour de nouveaux développements : c’est un transport en fin de vie.
Le SDK Python est-il aussi mature que celui en TypeScript ?
Oui, les deux sont au même niveau de fonctionnalités et de stabilité. La différence est plus communautaire : l’écosystème Node a plus d’adaptateurs pour les frameworks web (Next.js, Hono, Express), l’écosystème Python a plus de connecteurs vers les bases scientifiques et ML (DuckDB, Polars, MLflow). Choisissez en fonction de votre stack existante.
Comment savoir si mon serveur est bien implémenté ?
L’inspecteur officiel (mcp dev ou npx @modelcontextprotocol/inspector) est l’outil de référence. Il valide les schémas, joue les handshakes et vous laisse invoquer chaque outil. La conformance complète à la spec se vérifie en regardant les logs de la session initialize et en s’assurant que toutes les capabilities annoncées sont effectivement servies.
Ressources et références
- Spécification officielle MCP — version 2025-11-25
- SDK Python officiel (modelcontextprotocol/python-sdk)
- SDK TypeScript officiel (modelcontextprotocol/typescript-sdk)
- Tutoriel officiel — Authorization in MCP (OAuth 2.1)
- Serveurs de référence maintenus par l’équipe MCP
- Spécification JSON-RPC 2.0
- Brouillon OAuth 2.1