MCP remote : quand stdio ne suffit plus

Stdio suffit sur ta machine, pas au-delà. Streamable HTTP sans session depuis la spec de juillet 2026, un serveur MCP remote en TypeScript, puis OAuth 2.1.

Publié le Mis à jour le , mis à jour le 12 min de lecture

Un serveur MCP lancé en local ne sert qu'à la machine qui le lance. Cet article s'adresse à toi si tu as déjà écrit ou branché un serveur MCP en stdio et que tu veux le rendre accessible depuis Claude.ai, ton téléphone ou les postes d'une équipe. On y voit ce que le transport HTTP change, à quoi ressemble un serveur remote minimal en TypeScript, et comment l'authentification s'y greffe.

Si tu as lu l'introduction à MCPIA pour les développeurs · partie 310 minMCP pour les devs : relier tes LLM au monde réelMCP (Model Context Protocol) évite d'écrire un connecteur par client LLM et par source de données. Son architecture, puis un premier serveur MCP en TypeScript.Lire l'articleNouvel ongletUn bandeau t'y proposera de reprendre ici., tu sais qu'un serveur MCP expose des tools qu'un LLM peut appeler. Le serveur de cet article-là tournait en stdio : un processus local, lancé par Claude Desktop. Ici, on s'intéresse au transport, c'est-à-dire à la façon dont les messages circulent entre le client et le serveur.

Note

Cet article a été publié en mars 2026 et mis à jour en novembre 2026 pour la révision 2026-07-28 de la spec, qui supprime les sessions et le handshake d'initialisation. Le code utilise la v2 du SDK TypeScript.

Stdio reste le bon choix sur ta machine

Un serveur MCP stdio communique par l'entrée et la sortie standard. Le client le lance comme un sous-processus, lui écrit des messages JSON sur stdin et lit les réponses sur stdout.

Pour brancher un outil à Claude Code dans ton terminal, c'est le plus simple : pas de port, pas de réseau, pas d'authentification à gérer. La spec de juillet 2026 le garde d'ailleurs comme l'un de ses deux transports standards. Stdio n'est donc pas déprécié.

Ses limites tiennent à sa nature de processus local :

  • Le serveur tourne sur ta machine. Il faut le PC allumé et le bon runtime installé.
  • Seuls les clients capables de lancer un processus y accèdent. Claude Desktop et Claude Code le peuvent, Claude.ai dans un navigateur ou l'appli mobile ne le peuvent pas.
  • Un processus sert un utilisateur. Pour dix personnes, il faut dix installations.

Le besoin de remote apparaît donc dans des cas précis : tu veux retrouver tes tools depuis un autre appareil, les partager avec une équipe, ou en faire un produit.

De SSE à Streamable HTTP

Le premier transport réseau de MCP reposait sur Server-Sent Events (SSE), un mécanisme HTTP où le serveur pousse des messages sur une connexion gardée ouverte. Le client ouvrait cette connexion sur /sse pour recevoir les réponses, et envoyait ses requêtes sur un second endpoint.

En mars 2025 (spec 2025-03-26), MCP a introduit Streamable HTTP et déprécié ce modèle à deux endpoints :

# Ancien modèle (HTTP+SSE, déprécié)
Client ──GET──▶ /sse            (connexion persistante, réception)
Client ──POST─▶ /sse/messages   (envoi des requêtes)

# Streamable HTTP
Client ──POST──▶ /mcp           (tout passe par un seul endpoint)

Chaque message du client est un POST sur un endpoint unique. Le serveur répond par un objet JSON classique, ou par un flux SSE limité à cette requête quand il a des notifications de progression à envoyer avant le résultat.

La révision de juillet 2026 retire les sessions

Jusqu'en 2025, Streamable HTTP gardait une part d'état. Le client commençait par un handshake initialize, le serveur pouvait lui attribuer un identifiant dans un header Mcp-Session-Id, et un GET sur l'endpoint ouvrait un flux pour les messages à l'initiative du serveur.

La révision 2026-07-28 supprime ces trois mécanismes :

  • Plus de session au niveau du protocole, ni de header Mcp-Session-Id.
  • Plus de handshake initialize. Chaque requête transporte sa version de protocole et les capacités du client dans un champ _meta.
  • Plus d'endpoint GET. Un client qui veut être prévenu d'un changement, par exemple une liste de tools modifiée, ouvre un flux avec une requête POST subscriptions/listen.

Si ton serveur a besoin d'un état entre deux appels, il crée lui-même un identifiant et le client le lui repasse comme un argument de tool ordinaire.

Les informations utiles au routage sont aussi recopiées dans des headers HTTP : MCP-Protocol-Version, Mcp-Method, et Mcp-Name pour un appel de tool. Un load balancer ou une gateway peut ainsi router et filtrer sans lire le corps de la requête.

Ce que ça change pour l'infra

Un POST autonome traverse les load balancers, les proxies, les WAF et les CDN comme n'importe quelle requête d'API. Aucune instance n'a besoin de se souvenir du client : tu peux en mettre trois derrière un load balancer sans sticky sessions ni stockage partagé.

Chaque requête porte son token dans le header Authorization. Les outils habituels (rate limiting, WAF, logs d'accès) s'appliquent sans adaptation.

Les clients parlent HTTP

Claude.ai accepte les serveurs MCP remote depuis mi-2025, et les applis iOS et Android depuis juillet 2025. OpenAI a annoncé son adoption de MCP en mars 2025, Google en avril. Claude Code et Cursor acceptent une URL HTTP en plus des serveurs stdio.

En décembre 2025, MCP a rejoint la Linux Foundation, au sein de l'Agentic AI Foundation cofondée par Anthropic, Block et OpenAI. La spec n'est plus pilotée par un seul éditeur.

Concrètement, voilà la différence. En stdio, tu installes le serveur sur ton PC et tu configures Claude Desktop pour le lancer. Ça marche sur ce PC, avec ce client. Pour le partager, tu dupliques l'installation chez chacun.

En remote, tu déploies le serveur derrière une URL HTTPS et chaque client compatible s'y connecte après un passage par OAuth :

flowchart LR
    A["Claude.ai (web)"] --> S
    B["Claude (mobile)"] --> S
    C["Claude Code"] --> S
    D["Cursor"] --> S
    S["Serveur MCP<br/>Streamable HTTP"] --> ST[("Stockage<br/>S3, base, API")]
    S -. "vérifie les tokens émis par" .-> AS["Serveur d'autorisation<br/>OAuth 2.1"]

Anatomie d'un serveur MCP remote

Un serveur remote assemble un transport HTTP, une authentification et un stockage joignable par le réseau. On les prend dans cet ordre.

Un endpoint qui reçoit des POST

On part d'un projet Node.js 20 ou plus récent. La v2 du SDK TypeScript est découpée en paquets : le cœur du serveur, et des adaptateurs pour Express et pour les requêtes Node.

Terminal
npm install @modelcontextprotocol/server @modelcontextprotocol/express @modelcontextprotocol/node express zod
npm install -D tsx

Le serveur expose un seul tool, echo, qui renvoie le texte reçu :

src/server.ts
import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { toNodeHandler } from "@modelcontextprotocol/node";
import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
import * as z from "zod/v4";

// La factory est appelée à chaque requête HTTP : rien ne survit entre deux POST.
const handler = createMcpHandler(() => {
  const server = new McpServer({ name: "my-server", version: "1.0.0" });

  server.registerTool(
    "echo",
    {
      description: "Renvoie le texte reçu",
      inputSchema: z.object({ text: z.string() }),
    },
    async ({ text }) => ({ content: [{ type: "text", text }] })
  );

  return server;
});

const app = createMcpExpressApp(); // express.json() et contrôle du header Host
const mcp = toNodeHandler(handler);
app.post("/mcp", (req, res) => mcp(req, res, req.body));

app.listen(3000, "127.0.0.1");

createMcpHandler reçoit une fonction qui construit un McpServer neuf. Il l'appelle à chaque requête, ce qui rend l'endpoint sans état par construction. Si tu as un pool de connexions ou un cache, crée-le en dehors de la factory, une seule fois.

toNodeHandler fait le pont entre ce handler, qui parle Request et Response comme fetch, et les objets req et res d'Express.

Attention

Passe bien req.body en troisième argument. createMcpExpressApp a déjà lu le corps de la requête avec express.json(). Si tu montes toNodeHandler(handler) directement sur la route, l'adaptateur tente de relire un flux vide et répond Parse error: Invalid JSON.

Lance le serveur avec npx tsx src/server.ts, puis appelle le tool depuis un autre terminal :

Terminal
curl -s -X POST http://127.0.0.1:3000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/call' \
  -H 'Mcp-Name: echo' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "echo",
      "arguments": { "text": "bonjour" },
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'

La réponse arrive en JSON, sans handshake préalable :

JSON
{
  "result": {
    "content": [{ "type": "text", "text": "bonjour" }],
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "my-server",
        "version": "1.0.0"
      }
    }
  },
  "jsonrpc": "2.0",
  "id": 1
}

Tu retrouves dans cette requête ce que la révision de juillet impose : la version et les capacités dans _meta, et les trois headers recopiés du corps. Retire MCP-Protocol-Version et le serveur répond par une erreur -32020, parce que les headers et le corps ne concordent plus.

Les clients qui parlent encore le protocole de 2025 ne sont pas laissés de côté. Par défaut, le même handler répond à leur handshake initialize sans créer de session.

Attention

Ce serveur n'a aucune authentification. Il écoute sur 127.0.0.1, donc seule ta machine peut l'atteindre. Ne l'expose pas tel quel sur Internet : n'importe qui pourrait appeler tes tools. Avant de le déployer, il faut au minimum vérifier un token sur chaque requête, ce que la section suivante décrit.

Une authentification OAuth 2.1

L'authentification est optionnelle dans la spec, mais un serveur exposé sur Internet ne peut pas s'en passer. MCP s'appuie sur OAuth 2.1 : ton serveur MCP joue le rôle de serveur de ressources, et il délègue la connexion de l'utilisateur à un serveur d'autorisation, qui émet les tokens.

Le client n'a besoin que de l'URL du serveur MCP. Il découvre le reste en quatre temps.

sequenceDiagram
    participant C as Client MCP
    participant M as Serveur MCP
    participant A as Serveur d'autorisation
    C->>M: POST /mcp sans token
    M-->>C: 401 + adresse des métadonnées
    C->>M: GET /.well-known/oauth-protected-resource
    M-->>C: serveur d'autorisation à utiliser
    C->>A: GET des métadonnées du serveur d'autorisation
    A-->>C: endpoints, mécanismes supportés
    Note over C,A: l'utilisateur se connecte et consent (PKCE)
    A-->>C: access token
    C->>M: POST /mcp avec Authorization: Bearer
    M-->>C: réponse MCP

D'abord la découverte. Le client tente un appel sans token. Le serveur répond 401 avec un header WWW-Authenticate qui pointe vers ses métadonnées. Le client y lit quel serveur d'autorisation utiliser, puis interroge ce dernier :

GET https://mcp.example.com/.well-known/oauth-protected-resource
→ { "authorization_servers": ["https://auth.example.com"] }

GET https://auth.example.com/.well-known/oauth-authorization-server
→ { "authorization_endpoint": "...", "token_endpoint": "...",
    "client_id_metadata_document_supported": true }

Vient ensuite l'identification du client. Le serveur d'autorisation doit savoir qui lui demande un token, alors qu'il n'a jamais entendu parler de ce client. La spec 2026-07-28 recommande les Client ID Metadata Documents : le client_id est une URL HTTPS qui pointe vers un document JSON décrivant le client (son nom, ses URLs de redirection). Le serveur d'autorisation va le lire lui-même.

Les versions précédentes utilisaient le Dynamic Client Registration (DCR), où le client s'enregistrait par un POST et recevait un client_id. DCR est déprécié depuis juillet 2026. Il reste autorisé pour la compatibilité avec les serveurs d'autorisation qui ne gèrent pas encore les documents de métadonnées.

Troisième temps, PKCE, qui protège l'échange du code d'autorisation. Le client génère un secret à usage unique, le code_verifier, et n'en envoie d'abord que l'empreinte SHA-256. Il fournit le secret au moment d'échanger le code d'autorisation contre un token. Quelqu'un qui intercepte le code ne peut rien en faire.

Enfin, le token est lié à ton serveur. Le client indique dans sa demande à quel serveur MCP le token est destiné (paramètre resource). Ton serveur doit refuser un token émis pour un autre que lui.

Vu de l'utilisateur, tout ça se résume à coller une URL MCP dans Claude, voir un écran de consentement et cliquer sur « Autoriser ».

Côté code, le paquet @modelcontextprotocol/express fournit un middleware requireBearerAuth pour vérifier le token et un routeur mcpAuthMetadataRouter pour servir les métadonnées. Je garde leur mise en place pour la suite de cette série, parce qu'elle dépend beaucoup du serveur d'autorisation que tu choisis.

Un stockage joignable par le réseau

Un serveur remote ne lit pas ~/Documents. Il s'adresse à un backend réseau :

BackendCas d'usageExemples
S3-compatibleFichiers, notes, documentsAWS S3, Scaleway, R2, MinIO
Base de donnéesDonnées structuréesPostgreSQL, Supabase
API tierceProxy vers un serviceGitHub, Linear, Notion, Slack

Des tools sobres en tokens

En remote, chaque appel de tool est un aller-retour réseau, et chaque résultat consomme du contexte chez le LLM. Ça se prépare dès la conception des tools.

Économise les tokens. Un list_files qui renvoie 10 000 résultats d'un coup remplit le contexte pour rien. Prévois une pagination et un filtre par préfixe de chemin.

Propose des opérations fines. Avec un seul tool qui remplace tout le fichier, l'agent renvoie mille lignes pour en corriger une. Un patch ciblé ou un ajout en fin de fichier lui laissent choisir l'opération la moins coûteuse.

Les descriptions comptent autant que le code, parce que ce sont elles que le LLM lit pour décider quel tool appeler. Dis quand utiliser le tool, quand en préférer un autre, et donne un exemple de paramètre. La spec recommande aussi de renvoyer les tools dans un ordre stable d'un appel à l'autre : le client peut mettre la liste en cache, et le cache de prompt du LLM est mieux exploité.

Pourquoi beaucoup de serveurs restent en local

L'inertie joue. Les premiers exemples, tutos et templates étaient tous en stdio. Le coût aussi : un serveur remote demande un déploiement, une authentification et un peu d'infra, alors qu'un serveur stdio se lance avec un npx. Et pour un usage perso sur une seule machine, le local répond au besoin.

L'hébergement s'est simplifié depuis. Cloudflare documente par exemple le déploiement d'un serveur MCP remote sur Workers.

Ce que je ferais si je recommençais

Je partirais directement en Streamable HTTP dès qu'un deuxième appareil ou une deuxième personne entre en jeu, et je garderais stdio pour les outils qui n'ont de sens que sur mon poste.

Je construirais le serveur sans état dès le premier jour. Les sessions ont disparu de la spec, et un serveur qui ne retient rien entre deux requêtes se place derrière un load balancer sans précaution particulière.

Je mettrais OAuth dès le premier jour si quelqu'un d'autre que moi doit s'en servir, parce que le rajouter après coup est pénible.

Pour voir un cas réel, j'ai détaillé la construction de VaulkenProjet13 minVaulken : serveur MCP remote pour mes fichiersVaulken ouvre tes fichiers S3 à Claude via un serveur MCP remote. Ce qu'il fait, pour qui, et comment il est construit : OAuth, ton bucket, Qdrant, 12 tools.Lire l'articleNouvel ongletUn bandeau t'y proposera de reprendre ici, section 7., un serveur MCP remote qui donne à Claude l'accès à des fichiers stockés dans un bucket S3. L'article passe en revue ses douze tools.

MCP en pratique · partie 1 sur 1

Tu viens de finir la série.

    Voir en Markdown

    À lire ensuite

    IA pour les développeurs · 3

    MCP pour les devs : relier tes LLM au monde réel

    MCP (Model Context Protocol) évite d'écrire un connecteur par client LLM et par source de données. Son architecture, puis un premier serveur MCP en TypeScript.

    Cité dans cet article, à propos de l'introduction à MCP

    Projet

    Vaulken : serveur MCP remote pour mes fichiers

    Vaulken ouvre tes fichiers S3 à Claude via un serveur MCP remote. Ce qu'il fait, pour qui, et comment il est construit : OAuth, ton bucket, Qdrant, 12 tools.

    Cité dans cet article, à propos de la construction de Vaulken

    IA pour les développeurs · 2

    Agents IA : boucle agentique et pattern ReAct

    Construis un agent IA autonome en TypeScript avec Mistral : boucle agentique, pattern ReAct, planification, mémoire et garde-fous. Code complet inclus.

    Alexandre Simonet, développeur fullstack.
    J'écris ici sur les serveurs MCP, les agents et l'infra pour les faire tourner.