Déployer un serveur MCP sur EC2 après la modification de la spécification sans état
Déplacer un serveur MCP de localhost vers EC2 : HTTP streamable, nginx et authentification par jeton, puis quelle signification ont les sessions sans état et OAuth 2.1 pour un déploiement réel.
Sources: MCP Servers Explained: A Complete Guide to What I Learned Deploying One to AWS EC2
De localhost à un vrai serveur
Les tutoriels sur MCP restent souvent abstraits. Ils montrent un client à côté d’un serveur, comparent le protocole à un port USB-C, ou exécutent un exemple de cinq lignes sur localhost qui ne quitte jamais l’ordinateur portable du développeur. Le vrai travail commence après cela : un outil écrit localement doit fonctionner sur un serveur réel, derrière un proxy inversé réel, afin qu’un client réel puisse l’appeler via Internet. C’est un problème distinct qui fait l’objet de peu de discussions.
Le déploiement décrit ici commence par un serveur MCP local, qui est ensuite déplacé sur une instance EC2 et mis à disposition via le transport HTTP streamable, nginx exigeant au préalable un jeton porteur. Tout s’est déroulé sans problème. Peu de temps après, le protocole a été intégré à ce système. Le 28 juillet 2026, la plus importante version à ce jour de la spécification MCP est passée de version candidate à version finale, ce qui rend le protocole entièrement sans état. Les équipes indiquent souvent « prise en charge MCP » comme une seule catégorie, mais cela se divise en trois aspects : le transport, l’authentification et, depuis cette mise à jour, l’échelle. Une erreur dans l’un de ces aspects est ce qui distingue un outil de démonstration d’un outil fiable pour un usage en production.
Cette couverture aborde plusieurs questions : quels aspects du problème MCP définit et quels il laisse en suspens, pourquoi le protocole HTTP streamable a remplacé le transport basé sur SSE, comment nginx et un jeton porteur permettent d’exposer un serveur local sur EC2, quels sont les changements apportés par la réécriture sans état, pourquoi un jeton statique est considéré comme authentifiant mais ne répond pas aux exigences d’une authentification conforme aux spécifications, et quels éléments du déploiement ont dû être modifiés pour rester à jour.
Ce que MCP standardise et ce qu’il ne standardise pas
En théorie, le Model Context Protocol définit une seule chose : la manière dont une application d’IA trouve et invoque des outils, ainsi que lit des ressources fournies par un processus serveur distinct, via des messages JSON-RPC transmis par un transport défini. La logique des outils, la conception des agents et leur déploiement relèvent tous de domaines distincts. Il s’agit d’une norme de connexion, et non d’un framework. Auparavant, chaque application d’IA souhaitant accéder à une API interne nécessitait une intégration sur mesure. Avec MCP, un seul serveur sert tous les clients conformes.
Vu sous cet angle, il devient clair pourquoi le déploiement pose des difficultés. Le protocole décrit la structure des messages sans préciser si le serveur peut être atteint depuis un seul ordinateur portable ou depuis n’importe où, s’il est protégé par quelque mécanisme que ce soit, ou s’il peut gérer plusieurs clients en même temps. C’est au responsable de répondre à chacune de ces questions.
Pourquoi un déploiement public est différent
- Les serveurs locaux ne révèlent presque rien, tandis que les serveurs publics en révèlent beaucoup. Avec une URL publique, le serveur devient une API authentifiée comme n’importe laquelle, de sorte que la gestion des tokens et la validation des entrées sont tout aussi importantes.
- Les règles relatives aux sessions et à l’échelle étaient floues auparavant. Une mauvaise estimation entraînait soit un serveur qui échouait silencieusement sous charge simultanée, soit un serveur alourdi par des sessions persistantes dont il n’avait pas besoin.
- La spécification n’est pas figée. Une mise en œuvre basée sur des hypothèses datant de six mois peut cesser d’être conforme sans qu’aucune erreur ne se produise, car les clients négocient simplement avec une version plus ancienne du protocole.
L’architecture ressemble à ceci.
AI Client ── HTTPS POST ──▶ nginx (TLS termination, auth check, reverse proxy)
│
▼
MCP Server Process
(Streamable HTTP transport)
│
┌────────────┴────────────┐
Tools Resources
Un principe guide tout cela. Une fois qu’un serveur MCP dispose d’une URL publique, il faut le traiter comme n’importe quelle API authentifiée exposée depuis EC2, car structurellement il s’agit du même type de service. MCP n’est qu’un format de message supplémentaire, tandis que les problématiques de sécurité et d’échelle sont identiques à celles de tout backend.
1. Transport HTTP streamable
HTTP streamable repose sur un seul point de terminaison HTTP qui accepte des requêtes POST contenant des messages JSON-RPC. Il a remplacé le transport HTTP+SSE précédent, qui nécessitait un flux server-sent-events persistant en plus des appels HTTP classiques.
Un seul point de terminaison POST est quelque chose de familier pour tout proxy inversé, balanceur de charge ou passerelle API, ce qui permet de maintenir facilement un flux à long terme ouvert via nginx ou un ALB. Le trafic ressemble à celui d’une API HTTP ordinaire, et le bloc location de nginx reste court.
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_http_version 1.1;
proxy_read_timeout 300s;
}
Un conseil pour la production : même avec un transport à un seul point d’entrée, conservez un délai de timeout de lecture généreux pour le proxy. Les appels aux outils individuels peuvent légitimement durer plus longtemps qu’une requête API typique, et un timeout trop strict entraîne l’arrêt des appels longs valides en même temps que ceux qui sont réellement bloqués.
2. Authentification par jeton Bearer
Dans ce schéma, un jeton fixe est placé dans l’en-tête Authorization et vérifié avant même que la requête n’atteigne le processus serveur MCP, que ce soit dans nginx ou dans le middleware de l’application. Pour tout ce qui se trouve sur une URL publique, c’est le niveau de sécurité le moins acceptable, mais c’est un premier pas raisonnable. Cependant, le jeton ne expire jamais, n’est pas lié à un serveur particulier et ne offre aucune protection s’il finit dans un journal. Il indique simplement qu’une requête contient des identifiants.
Un exemple de middleware FastAPI commence par les importations.
from fastapi import Request, HTTPException
Vient ensuite le virement lui-même.
VALID_TOKEN = "your-rotated-secret-token"async def verify_bearer(request: Request):
auth = request.headers.get("authorization", "")
if auth != f"Bearer {VALID_TOKEN}":
raise HTTPException(status_code=401, detail="Unauthorized")
Un autre conseil : évitez d’inclure l’en-tête brut Authorization dans les journaux d’accès. Les formats par défaut de nginx et des journaux d’application rendent facile son oubli, et une fois présent, le fichier de journal devient une clé d’accès divulguée.
3. L’absence d’état, cœur de la réécriture de juillet 2026
Avec la spécification du 28/07/2026, les sessions au niveau du protocole disparaissent. Il n’y a plus d’en-tête Mcp-Session-Id, et le transport abandonne également le flux GET à longue durée utilisé par les versions précédentes pour maintenir l’alignement entre le client et le serveur au cours d’une conversation. Chaque requête contient tout ce dont elle a besoin.
Le cœur du problème réside dans l’échelle. Le modèle ancien permettait à un serveur de conserver des informations sur un client entre différentes requêtes, et lorsque plusieurs instances étaient derrière un équilibreur de charge, cette mémoire exigeait des sessions persistantes. Sans état au niveau du protocole, n’importe quelle instance peut gérer n’importe quelle requête, ce qui permet une véritable échelle horizontale et libère les clients de leur dépendance à une seule machine.
En pratique, un serveur qui conserve en mémoire des informations sur un client entre les requêtes cessera de fonctionner selon les nouvelles spécifications. Tout état important doit être transféré dans la charge utile de la requête ou dans un stockage externe comme Redis, accessible par toutes les instances.
4. Requêtes multi-allers-retours
Même un protocole sans état a besoin d’un moyen pour que le serveur pose une question au client au cours d’une requête, comme demander une confirmation ou signaler l’absence d’un paramètre. La nouvelle spécification résout ce problème grâce aux Multi Round-Trip Requests. Au lieu de maintenir un flux ouvert, le serveur répond par un InputRequiredResult qui énumère ses questions ainsi qu’une valeur requestState opaque. Le client rassemble ces réponses, envoie à nouveau la requête initiale, y ajoute ces réponses et renvoie l’état inchangé.
C’est ce qui rend possible un véritable fonctionnement sans état. Comme tout ce dont le serveur a besoin pour reprendre la transmission se trouve dans la charge utile renvoyée par le client, n’importe quelle instance, pas nécessairement celle qui a initié la requête, peut reprendre la tentative et la terminer.
Les clients doivent laisser requestState intact : pas d’inspection, pas de modification. Il faut le conserver et le renvoyer exactement tel qu’il est arrivé, sinon une reprise qui dépend de son intégrité pourrait échouer.
5. Renforcement des autorisations
La spécification révisée oriente les serveurs MCP distants vers OAuth 2.1 avec PKCE et exige des indicateurs de ressource (RFC 8707), qui lient un jeton émis au seul serveur pour lequel il a été conçu.
Le risque principal est la réutilisation de tokens. Si un token porteur statique est divulgué, n’importe quel serveur qui l’accepte peut être ciblé. Un token lié à une ressource comble cette faille, de sorte qu’un token émis pour un serveur MCP ne fonctionnera pas sur un autre, même si les deux dépendent du même fournisseur d’identité.
Une configuration basée sur des tokens porteurs, qui est le point de départ pour beaucoup, ne répond pas aux exigences de « cet endpoint nécessite une authentification ». Elle ne satisfait pas non plus aux critères d’interopérabilité et de protection contre les retransmissions attendus d’un serveur distant conforme aux nouvelles spécifications, et ce manque doit être comblé avant que l’URL ne parvienne à quiconque en dehors de l’équipe principale.
6. Politique de dépréciation et extensions
Les fonctionnalités décrites dans les spécifications suivent désormais un cycle de vie formel comprenant les étapes Active, Deprecated et Removed, avec au moins douze mois entre la dépréciation et la suppression définitive. De nouvelles fonctionnalités, telles que des interfaces utilisateur générées par le serveur ou des tâches longues durée, sont d’abord proposées sous forme d’extensions optionnelles plutôt que d’être intégrées directement au noyau. Certaines fonctionnalités plus anciennes, comme les accès racines, l’échantillonnage et le journalisation dans leur forme originale, sont en voie de dépréciation au profit de solutions de remplacement.
Pour les opérateurs, cela signifie qu’une intégration fonctionnelle ne sera pas perturbée du jour au lendemain par une future révision, et que de nouvelles fonctionnalités peuvent être adoptées de manière délibérée, sans pression de devoir suivre le rythme de toutes les ajouts prévus dans la spécification.
Une présentation complète du processus
Le parcours allant d’un prototype local à un déploiement EC2 conforme à la spécification se déroule comme suit :
- Tests locaux. Le serveur fonctionne via stdio avec un client local, sans réseau ni authentification, ce qui permet des itérations rapides.
- Passage à HTTP en flux continu. Le serveur est réconfiguré pour utiliser le transport HTTP en flux continu et déployé sur une instance EC2.
- nginx en première ligne. nginx termine le chiffrement TLS, vérifie le jeton d’authentification avant que quoi que ce soit n’atteigne le processus MCP, puis fait un reverse-proxying de la requête.
- Appel de outil simple. Une requête POST contient une demande JSON-RPC, et le résultat revient dans cette même requête et réponse.
- Négociation de version. Avec la version finale de la spécification du 28/07/2026, le client et le serveur négocient une révision du protocole. Un client qui n’a pas été mis à jour passe automatiquement à la version du 25/11/2025 sans planter complètement.
- Des informations supplémentaires sont nécessaires. Plutôt que de laisser la connexion ouverte, le serveur répond par un
InputRequiredResultindiquant ce qu’il doit encore demander. - Reprise. Le client répète l’appel en y ajoutant les réponses ainsi que le
requestStatereçu. La tentative de réessai n’est pas dépendante d’une instance particulière, donc n’importe quel ordinateur derrière nginx peut la gérer. - Échecs d’authentification. Les requêtes sans jeton ou avec un jeton invalide sont rejetées par nginx. Le code de l’application ne les rencontre jamais.
Cas particuliers
Opérations lentes. Les tâches qui dépendaient autrefois d’un flux maintenu ouvert sur toute la durée devraient passer à l’extension Tasks. Créer à nouveau une connexion persistante sur un transport conçu sans elle est la mauvaise approche.
Outils qui ne seront jamais utilisés en mode distant. Un serveur qui ne s’exécute qu’en local à côté de son client est le mieux servi par stdio, et presque aucune des préoccupations liées au déploiement ne s’applique dans ce cas.
Clients de versions mixtes. Comme les clients plus anciens passent automatiquement en mode 2025-11-25, un déploiement peut raisonnablement prendre en charge les deux versions du protocole pendant un certain temps, de sorte que tous les clients n’ont pas besoin d’être mis à jour en même temps que le serveur.
Problèmes de mise à l’échelle et en production
Instances multiples. Dans le cadre du modèle de session, pour maintenir un client lié à l’instance qui le connaissait, il fallait recourir à des sessions persistantes ou à un stockage de sessions partagé. Ce besoin a disparu, et pour quiconque gère plusieurs serveurs, c’est l’avantage principal de cette réécriture.
TLS sur une flotte en expansion. Gérer un certificat sur chaque instance EC2 devient rapidement fastidieux. Un équilibreur de charge géré qui héberge un certificat ACM en amont de la flotte exige beaucoup moins d’efforts que la configuration TLS avec nginx sur chaque serveur.
Dépôt et rotation des tokens. Les tokens porteurs statiques présentent des limites en termes de sécurité, et ils représentent également un problème opérationnel au-delà de quelques clients, car chaque rotation nécessite une coordination manuelle. La délivrance basée sur OAuth 2.1 résout à la fois le manque de sécurité et la charge liée à la rotation.
Suivi des appels sans sessions côté serveur. Comme aucune session ne relie une série d’appels sur le serveur, les IDs de corrélation contenus dans la charge utile de la requête deviennent le seul moyen fiable pour reconstituer ce qu’a fait une seule interaction avec un agent au cours de plusieurs appels.
Gestion de la reprise sur le client
La configuration nginx et le middleware bearer-check gèrent le transport ainsi que l’authentification de base. Voici un modèle minimal pour la partie client d’une demande à plusieurs allers-retours.
async def call_tool_with_resume(client, tool_name, params):
result = await client.call_tool(tool_name, params)
if result.get("type") == "InputRequiredResult":
answers = collect_answers(result["questions"])
return await client.call_tool(
tool_name,
{**params, "answers": answers, "requestState": result["requestState"]},
)
return result
Ce qui compte, c’est la dernière ligne, où requestState revient exactement tel que le serveur l’a fourni.
Pièges courants
- Considérer un système « déployé » comme prêt pour la production. nginx associé à un jeton porteur ne remplit que la condition minimale, et non toutes les exigences. Appliquez aux points d’entrée MCP les mêmes normes qu’à toute API publique : authentification adéquate, enregistrement minutieux des activités et limitation des fréquences d’accès avant que l’URL ne soit largement partagée.
- Se fier à la mémoire du serveur. Certaines premières versions de déploiement comptaient silencieusement sur le fait que le serveur se souvienne d’un client ayant effectué une demande précédente. Prévoyez que chaque demande puisse atteindre une instance différente de la précédente, car la spécification actuelle le permet explicitement.
- Fixation d’une seule version de protocole. Un client verrouillé sur une révision donnée cesse de fonctionner lorsque le serveur est mis à jour. Intégrez une négociation avec un mécanisme de fallback adapté, afin que la mise à jour d’un des deux côtés n’empêche pas le fonctionnement de l’autre.
- Ignorance de la vérification d’authentification pour un outil interne. Un outil actuellement interne pourrait être transmis à une équipe partenaire plus tard, et à ce moment-là un jeton statique ne suffira pas. Comme le serveur pourrait quitter un ordinateur portable ou une VPC, optez dès le départ pour OAuth 2.1 ainsi que pour des indicateurs de ressources.
Pratiques fiables en environnement de production
- Évitez des états qui empêchent l’échelle de votre infrastructure. Tout ce qui doit être conservé entre les appels doit être stocké dans la charge utile ou dans un stock externe.
- Associez chaque jeton à la ressource prévue pour lui, car un jeton accepté par tous les serveurs en qui vous avez confiance sera tôt ou tard utilisé à un endroit où il ne devrait pas l’être.
- Négociez les versions de protocole et n’en supposez pas une en particulier. Les clients et les serveurs mettent rarement à jour leurs versions le même jour, il faut donc prévoir cette différence.
- Vérifiez l’authentification au niveau de la frontière. Lorsque nginx rejette une demande invalide, le processus MCP reste plus simple et expose moins d’informations.
- Laissez de côté les champs opaques.
requestStateet d’autres similaires existent pour que le serveur puisse s’appuyer sur les données retournées ; par conséquent, les clients ne devraient ni les lire ni les modifier.
Impact sur le déploiement
En théorie, MCP est simple. Le déploiement implique les décisions importantes, et la mise à jour de la spécification du 28 juillet 2026 les a modifiées. Streamable HTTP est plus facile à mettre en œuvre derrière une infrastructure ordinaire ; le caractère sans état rend l’ajout d’une instance EC2 une option réaliste plutôt qu’un projet de gestion de sessions, et le modèle d’autorisation convient désormais à un serveur destiné au grand public. Rien de tout cela n’est difficile une fois bien compris, et c’est justement ce que les exposés théoriques omettent.
Lectures complémentaires
- Notes pratiques : Protocole Contexte Modèle (MCP) expliqué : Guide pour débutants — Guide pas à pas des Notes pratiques : Protocole Contexte Modèle (MCP) expliqué : Guide pour débutants, incluant des contrats, des vérifications et des blocs de code prêts à l’emploi pour les équipes qui implémentent ce modèle.
- Notes pratiques : Systèmes RAG : Le guide complet de zéro à héros (édition 2026) — Guide pas à pas des Notes pratiques : Systèmes RAG : Le guide complet de zéro à héros (édition 2026), comprenant des contrats, des vérifications et des blocs de code prêts à l’emploi pour les équipes qui implémentent ce modèle.