Retour aux articles
14 MIN READ

MCP Avancé : Transports, Sampling et Patterns de Production

By Learnia AI Research Team

MCP Avancé : Transports, Sampling et Patterns de Production

Le Model Context Protocol (MCP) va bien au-delà d'une simple interface client-serveur. Ce guide explore les mécanismes de transport, le sampling (interaction modèle initiée par le serveur), les notifications, l'accès au système de fichiers et les patterns de déploiement en production. Si vous découvrez MCP, commencez par notre guide d'introduction au MCP avec Claude Code.


Les 3 Mécanismes de Transport MCP

Le transport est la couche qui détermine comment les messages JSON-RPC circulent entre le client et le serveur MCP. Chaque transport a ses compromis en termes de complexité, performance et cas d'usage.

Loading diagram…

STDIO — Transport Local

Le transport le plus simple : le client lance le serveur comme un sous-processus et communique via stdin/stdout.

// Serveur MCP avec transport STDIO (TypeScript SDK)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "file-analyzer",
  version: "1.0.0",
});

// Déclarer un outil
server.tool(
  "analyze_file",
  "Analyse le contenu d'un fichier",
  { path: { type: "string", description: "Chemin du fichier" } },
  async ({ path }) => {
    const content = await fs.readFile(path, "utf-8");
    return {
      content: [{ type: "text", text: `Fichier: ${content.length} caractères` }],
    };
  }
);

// Démarrer avec STDIO
const transport = new StdioServerTransport();
await server.connect(transport);

Avantages : Zéro configuration réseau, latence minimale, isolation par processus. Limites : Local uniquement, un client par serveur, pas de reprise de session.

SSE — Server-Sent Events (Legacy)

SSE utilise HTTP pour les requêtes client → serveur et un flux d'événements pour les messages serveur → client.

// Serveur MCP avec transport SSE
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  await transport.handlePostMessage(req, res);
});

app.listen(3001);

⚠️ SSE est considéré legacy dans la spécification MCP actuelle. Préférez Streamable HTTP pour les nouveaux projets.

Streamable HTTP — Le Standard Production

Le transport recommandé pour la production. Le client envoie des requêtes HTTP POST, et le serveur peut répondre soit avec du JSON classique, soit ouvrir un flux SSE pour le streaming.

// Serveur MCP avec transport Streamable HTTP
import { StreamableHTTPServerTransport } from
  "@modelcontextprotocol/sdk/server/streamableHttp.js";

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID(),
  onsessioninitialized: (sessionId) => {
    sessions.set(sessionId, transport);
  },
});

app.post("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];
  // Routage vers la session existante ou création
  await transport.handleRequest(req, res);
});

// Support GET pour le streaming serveur → client
app.get("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];
  await transport.handleSSEStream(req, res);
});

// Support DELETE pour la terminaison de session
app.delete("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];
  sessions.delete(sessionId);
  res.status(200).end();
});

Avantages : Compatible avec l'infrastructure HTTP existante, reprise de session, streaming optionnel, multi-client.


Le Sampling : Quand le Serveur Demande au Modèle

Le sampling est l'une des fonctionnalités les plus puissantes et les moins comprises de MCP. Il inverse le flux habituel : au lieu que le client appelle le serveur, c'est le serveur qui demande au client d'effectuer un appel LLM.

Loading diagram…

Pourquoi le Sampling ?

Certaines opérations côté serveur nécessitent du « raisonnement IA » intermédiaire. Par exemple :

  • Analyse de code : Le serveur lit les fichiers, mais a besoin que le LLM identifie les patterns problématiques.
  • Résumé itératif : Le serveur collecte des données, demande un résumé, puis affine.
  • Décision contextuelle : Le serveur a besoin du jugement du modèle pour choisir l'étape suivante.
// Côté serveur : demander un sampling au client
server.tool(
  "smart_refactor",
  "Refactorise intelligemment un fichier",
  { filePath: { type: "string" } },
  async ({ filePath }) => {
    const code = await fs.readFile(filePath, "utf-8");

    // Demander au client d'appeler le LLM
    const analysis = await server.createMessage({
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `Analyse ce code et identifie les améliorations :\n\n${code}`,
          },
        },
      ],
      maxTokens: 2048,
      modelPreferences: {
        hints: [{ name: "claude-sonnet-4-20250514" }],
      },
    });

    // Utiliser la réponse du LLM pour effectuer le refactoring
    return {
      content: [{ type: "text", text: analysis.content.text }],
    };
  }
);
# Côté serveur en Python : sampling
@server.tool("smart_refactor")
async def smart_refactor(file_path: str) -> str:
    code = Path(file_path).read_text()

    # Le serveur demande au client d'appeler le LLM
    result = await server.create_message(
        messages=[
            {
                "role": "user",
                "content": {
                    "type": "text",
                    "text": f"Analyse ce code et identifie les améliorations :\n\n{code}",
                },
            }
        ],
        max_tokens=2048,
        model_preferences={
            "hints": [{"name": "claude-sonnet-4-20250514"}]
        },
    )

    return result.content.text

Bonnes Pratiques du Sampling

  1. Fournissez des modelPreferences avec des hints, pas des exigences. Le client choisit le modèle final.
  2. Limitez maxTokens au strict nécessaire — le sampling consomme le budget de l'utilisateur.
  3. Messages clairs : Le prompt envoyé au LLM doit être auto-suffisant (pas de contexte implicite).
  4. Gérez le refus : Le client peut refuser un sampling. Prévoyez toujours un fallback.

Notifications et Progress Tokens

MCP est un protocole bidirectionnel qui supporte les notifications — des messages qui n'attendent pas de réponse.

Notifications Serveur → Client

// Le serveur notifie un changement de ressource
server.notification({
  method: "notifications/resources/updated",
  params: { uri: "file:///project/config.json" },
});

// Le serveur notifie que sa liste d'outils a changé
server.notification({
  method: "notifications/tools/list_changed",
});

Progress Tokens — Suivi des Opérations Longues

Pour les opérations longues, le client peut envoyer un progressToken que le serveur utilise pour émettre des mises à jour d'avancement.

// Côté client : envoyer un progress token
const result = await client.callTool({
  name: "index_repository",
  arguments: { repoPath: "/project" },
  _meta: { progressToken: "idx-001" },
});

// Côté serveur : émettre des notifications de progression
server.tool(
  "index_repository",
  "Indexe un repository entier",
  { repoPath: { type: "string" } },
  async ({ repoPath }, { meta }) => {
    const files = await glob(`${repoPath}/**/*`);

    for (let i = 0; i < files.length; i++) {
      // Notifier la progression
      await server.notification({
        method: "notifications/progress",
        params: {
          progressToken: meta.progressToken,
          progress: i + 1,
          total: files.length,
          message: `Indexation: ${files[i]}`,
        },
      });

      await indexFile(files[i]);
    }

    return {
      content: [{ type: "text", text: `${files.length} fichiers indexés` }],
    };
  }
);

Accès au Système de Fichiers

L'un des cas d'usage les plus courants de MCP est l'accès sécurisé au système de fichiers. Le serveur @modelcontextprotocol/server-filesystem officiel illustre les patterns recommandés.

Principe du Moindre Privilège

// Configuration : limiter les répertoires accessibles
const ALLOWED_DIRS = [
  "/home/user/project",
  "/home/user/docs",
];

function validatePath(requestedPath: string): string {
  const resolved = path.resolve(requestedPath);
  const isAllowed = ALLOWED_DIRS.some(
    (dir) => resolved.startsWith(dir)
  );

  if (!isAllowed) {
    throw new Error(`Accès refusé: ${resolved} hors des répertoires autorisés`);
  }
  return resolved;
}

// Outil de lecture sécurisé
server.tool(
  "read_file",
  "Lit le contenu d'un fichier dans les répertoires autorisés",
  { path: { type: "string" } },
  async ({ path: filePath }) => {
    const safePath = validatePath(filePath);
    const content = await fs.readFile(safePath, "utf-8");
    return {
      content: [{ type: "text", text: content }],
    };
  }
);

Exposition de Fichiers comme Ressources MCP

Les ressources MCP permettent d'exposer des fichiers au client de manière structurée :

// Exposer des fichiers de configuration comme ressources
server.resource(
  "project-config",
  "file:///project/config.json",
  "Configuration du projet",
  async () => {
    const config = await fs.readFile("/project/config.json", "utf-8");
    return {
      contents: [{
        uri: "file:///project/config.json",
        mimeType: "application/json",
        text: config,
      }],
    };
  }
);

Modèle de Sécurité MCP

La sécurité MCP repose sur plusieurs couches. Comprendre ce modèle est essentiel avant tout déploiement en production. Pour des patterns complémentaires sur l'architecture d'agents sécurisés, consultez notre guide des patterns d'architecture d'agents.

Loading diagram…

Les 6 Principes de Sécurité MCP

  1. Validation des entrées : Chaque argument d'outil doit être validé (type, format, limites).
  2. Moindre privilège : Un serveur ne devrait exposer que les outils strictement nécessaires.
  3. Human-in-the-loop : Les opérations sensibles nécessitent une approbation explicite.
  4. Journalisation : Chaque invocation d'outil doit être loggée avec ses paramètres et résultats.
  5. Isolation : L'exécution de code doit être sandboxée (containers, VMs, etc.).
  6. Authentification : Utiliser OAuth 2.1 pour les transports réseau.
// Middleware de sécurité pour un serveur MCP en production
function securityMiddleware(handler) {
  return async (req, res) => {
    // 1. Vérifier le token OAuth
    const token = req.headers.authorization?.replace("Bearer ", "");
    if (!token || !await verifyOAuthToken(token)) {
      return res.status(401).json({ error: "Non autorisé" });
    }

    // 2. Rate limiting
    const clientId = extractClientId(token);
    if (await isRateLimited(clientId)) {
      return res.status(429).json({ error: "Trop de requêtes" });
    }

    // 3. Journalisation
    logger.info("mcp_request", {
      clientId,
      method: req.body?.method,
      params: sanitize(req.body?.params),
      timestamp: new Date().toISOString(),
    });

    return handler(req, res);
  };
}

Patterns de Déploiement en Production

Pattern 1 : Gateway MCP

Un reverse proxy centralise l'authentification, le routage et le monitoring pour plusieurs serveurs MCP.

// Gateway MCP avec Express
import express from "express";

const app = express();
const SERVER_REGISTRY = {
  "file-server": { url: "http://localhost:3001/mcp", auth: "internal" },
  "db-server": { url: "http://localhost:3002/mcp", auth: "oauth" },
  "search-server": { url: "http://localhost:3003/mcp", auth: "api-key" },
};

app.post("/mcp/:serverId", securityMiddleware(async (req, res) => {
  const { serverId } = req.params;
  const server = SERVER_REGISTRY[serverId];

  if (!server) {
    return res.status(404).json({ error: "Serveur inconnu" });
  }

  // Proxy vers le serveur MCP cible
  const response = await fetch(server.url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "mcp-session-id": req.headers["mcp-session-id"],
    },
    body: JSON.stringify(req.body),
  });

  const data = await response.json();
  res.json(data);
}));

Pattern 2 : Multi-Serveur avec Discovery

# Client MCP avec découverte dynamique de serveurs (Python)
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

class McpOrchestrator:
    def __init__(self):
        self.servers: dict[str, ClientSession] = {}

    async def discover_and_connect(self, registry_url: str):
        """Découvre et connecte les serveurs MCP disponibles."""
        async with httpx.AsyncClient() as http:
            registry = await http.get(registry_url)
            servers = registry.json()["servers"]

        for server_info in servers:
            async with streamablehttp_client(server_info["url"]) as (
                read, write, _
            ):
                session = ClientSession(read, write)
                await session.initialize()
                self.servers[server_info["name"]] = session

                tools = await session.list_tools()
                print(f"  {server_info['name']}: {len(tools.tools)} outils")

    async def call_tool(self, server_name: str, tool_name: str, args: dict):
        """Appelle un outil sur un serveur spécifique."""
        session = self.servers[server_name]
        result = await session.call_tool(tool_name, args)
        return result

Pattern 3 : Gestion d'Erreurs Robuste

// Gestion d'erreurs avec retry et circuit breaker
class ResilientMcpClient {
  private circuitOpen = false;
  private failureCount = 0;
  private readonly MAX_FAILURES = 5;
  private readonly RETRY_DELAY = 1000;

  async callTool(name: string, args: Record<string, unknown>) {
    if (this.circuitOpen) {
      throw new Error("Circuit ouvert — serveur indisponible");
    }

    for (let attempt = 0; attempt < 3; attempt++) {
      try {
        const result = await this.session.callTool({ name, arguments: args });
        this.failureCount = 0; // Reset on success
        return result;
      } catch (error) {
        this.failureCount++;

        if (this.failureCount >= this.MAX_FAILURES) {
          this.circuitOpen = true;
          setTimeout(() => {
            this.circuitOpen = false;
            this.failureCount = 0;
          }, 30_000); // Réessayer après 30s
        }

        if (attempt < 2) {
          await new Promise((r) =>
            setTimeout(r, this.RETRY_DELAY * (attempt + 1))
          );
        }
      }
    }
    throw new Error(`Échec après 3 tentatives: ${name}`);
  }
}

Pour d'autres patterns de production avec Claude, consultez notre guide sur le prompt caching et le protocole MCP.


Checklist de Déploiement Production

Avant de déployer un serveur MCP en production, validez cette checklist :

CatégorieVérificationPriorité
TransportStreamable HTTP configuré avec sessionsCritique
AuthOAuth 2.1 implémenté et testéCritique
TLSHTTPS uniquement, certificats validesCritique
ValidationToutes les entrées d'outils validéesHaute
Rate LimitLimites par client configuréesHaute
LoggingChaque invocation journaliséeHaute
ErreursCircuit breaker / retry implémentéMoyenne
MonitoringMétriques de latence et taux d'erreursMoyenne
SandboxExécution de code isoléeSelon usage
ProgressProgress tokens pour les ops longuesSelon usage

Pour intégrer ces patterns dans un workflow d'équipe, consultez notre guide sur la collaboration d'équipe avec Claude Code. Pour approfondir l'utilisation des outils avec Claude, voir notre guide complet du tool use.


Conclusion

MCP en production exige une compréhension fine des mécanismes de transport, du modèle de sécurité et des patterns de résilience. Les points clés à retenir :

  • Utilisez Streamable HTTP pour tout déploiement réseau — SSE est legacy.
  • Le sampling ouvre des possibilités uniques mais exige un contrôle strict côté client.
  • Les notifications et progress tokens améliorent l'UX pour les opérations longues.
  • La sécurité est multicouche : transport, authentification, validation, sandboxing.
  • Les patterns Gateway et Circuit Breaker sont essentiels en production.
Newsletter

Weekly AI Insights

Tools, techniques & news — curated for AI practitioners. Free, no spam.

Free, no spam. Unsubscribe anytime.

FAQ

Quelles sont les différences entre les trois mécanismes de transport MCP ?+

STDIO est le plus simple, idéal pour les processus locaux avec une latence minimale. SSE (Server-Sent Events) permet une communication unidirectionnelle du serveur vers le client via HTTP. Streamable HTTP est le transport recommandé pour la production : il combine requêtes HTTP classiques et streaming SSE optionnel, avec support natif de la reprise de session.

Qu'est-ce que le sampling dans le protocole MCP ?+

Le sampling permet à un serveur MCP de demander au client d'effectuer un appel LLM. Cela inverse le flux habituel : au lieu que le client appelle le serveur, c'est le serveur qui initie une interaction avec le modèle via le client, tout en gardant l'utilisateur dans la boucle de contrôle.

Comment sécuriser un serveur MCP en production ?+

Les bonnes pratiques incluent : valider toutes les entrées côté serveur, implémenter le principe du moindre privilège pour les outils, utiliser OAuth 2.1 pour l'authentification, journaliser toutes les invocations d'outils, limiter le rate des requêtes et sandboxer l'exécution de code si nécessaire.

Quand utiliser les progress tokens dans MCP ?+

Les progress tokens sont utiles pour les opérations longues (indexation de fichiers, téléchargements, analyses complexes). Le client envoie un _meta.progressToken dans sa requête, et le serveur émet des notifications/progress avec l'avancement, permettant au client d'afficher une barre de progression.