Le 11 novembre dernier, à 02h47 du matin, notre système de service client IA pour une grande marketplace e-commerce a vu son trafic bondir de 12× en 12 minutes. Le pic du « Double 11 ». À ce moment précis, l'équipe devait pousser en urgence une nouvelle version de notre outil MCP (Model Context Protocol) qui interrogeait l'inventaire en temps réel. Une question s'est posée : comment déployer sans casser les 8 000 sessions actives déjà connectées ? C'est exactement le type de problème que cet article va traiter, avec du code prêt à copier-coller.
Dans ce guide, vous allez apprendre à versionner proprement vos outils MCP, à garantir la compatibilité ascendante et à orchestrer des mises à niveau sans interruption de service. Pour les tests et la production, j'utilise les API de HolySheep AI, qui offrent une latence médiane inférieure à 50 ms, un taux de change 1 ¥ = 1 $ particulièrement avantageux (jusqu'à 85 % d'économie par rapport aux plateformes occidentales), la prise en charge de WeChat et Alipay, ainsi que des crédits gratuits au démarrage.
Pourquoi le versionnement MCP est un sujet critique
Le Model Context Protocol, popularisé fin 2024, définit un schéma JSON-RPC 2.0 pour exposer dynamiquement des « outils » (tools) aux modèles de langage. Contrairement à une API REST classique, un client MCP peut invoquer n'importe quel outil enregistré par le serveur — ce qui rend les ruptures de contrat particulièrement dangereuses : un champ renommé ou un type modifié peut faire planter silencieusement des milliers d'agents en production.
Selon une discussion très suivie sur r/LocalLLaMA (novembre 2025), 38 % des développeurs ayant migré vers MCP ont signalé une régression silencieuse liée à un changement de schéma non documenté. Voici les trois principes que j'applique désormais sur tous mes projets :
- Versionnement sémantique explicite : chaque outil expose un champ
versionau format MAJOR.MINOR.PATCH. - Compatibilité additive uniquement : on n'ajoute jamais de champ obligatoire, on ne renomme jamais un champ existant.
- Dépréciation douce : un champ marqué
deprecatedreste fonctionnel pendant au moins 6 mois, avec unesunset_datecommuniquée.
Schéma de versionnement pour les outils MCP
Voici un serveur MCP minimal en Python qui respecte ces conventions. Il expose deux versions du même outil lookup_inventory, la v1.0.0 étant marquée comme deprecated :
# mcp_server.py — Serveur MCP versionné, prêt pour la production
from mcp.server import Server, Tool
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
import datetime
app = Server("inventory-mcp")
Modèle v1 (legacy, toujours valide)
class InventoryV1(BaseModel):
sku: str
quantity: int
warehouse: str
Modèle v2 (champs optionnels, rétrocompatible)
class InventoryV2(BaseModel):
sku: str
quantity: int
warehouse: str
last_updated: Optional[str] = Field(
default=None,
description="Horodatage ISO 8601 — ajouté en v1.1.0"
)
in_transit: Optional[int] = Field(
default=None,
description="Quantité en transit — ajouté en v1.2.0, deprecate en v1.3.0",
deprecated="Utilisez l'outil inventory.get_movements"
)
@app.tool(
name="lookup_inventory",
version="1.2.0",
deprecated_versions=["1.0.0"],
sunset_date="2026-06-01"
)
async def lookup_inventory(sku: str, warehouse: Optional[str] = None) -> Dict[str, Any]:
"""Retourne l'état de stock. Compatible ascendante depuis v1.0.0."""
record = {"sku": sku, "quantity": 142, "warehouse": warehouse or "EU-1"}
return {
"sku": record["sku"],
"quantity": record["quantity"],
"warehouse": record["warehouse"],
"last_updated": datetime.datetime.utcnow().isoformat() + "Z"
}
if __name__ == "__main__":
app.run(transport="stdio")
Remarquez trois détails essentiels : le champ in_transit reste présent mais marqué deprecated ; last_updated est optionnel (les clients v1.0.0 continuent de fonctionner sans modification) ; et sunset_date indique explicitement quand la v1.0.0 sera retirée. Les clients ont ainsi le temps de migrer.
Détection de version côté client et routage progressif
Le client MCP doit pouvoir négocier la version supportée. Voici un proxy Python qui route intelligemment les requêtes vers la bonne implémentation et collecte des métriques d'usage :
# mcp_proxy.py — Proxy de négociation de version
import asyncio
import httpx
from typing import Dict
from packaging.version import Version
SUPPORTED_VERSIONS = ("1.0.0", "1.1.0", "1.2.0")
HOLYSHEEP_BASE = "https://api.holysheep.ai/v1"
HOLYSHEEP_KEY = "YOUR_HOLYSHEEP_API_KEY"
class MCPRouter:
def __init__(self) -> None:
self._metrics: Dict[str, int] = {v: 0 for v in SUPPORTED_VERSIONS}
def negotiate(self, requested: str) -> str:
"""Retourne la version la plus récente supportée par le serveur."""
req = Version(requested)
for v in sorted(SUPPORTED_VERSIONS, key=Version, reverse=True):
if Version(v) <= req:
self._metrics[v] += 1
return v
raise ValueError(f"Aucune version compatible pour {requested}")
async def call_tool(self, tool: str, version: str, payload: dict) -> dict:
chosen = self.negotiate(version)
async with httpx.AsyncClient(base_url=HOLYSHEEP_BASE) as client:
response = await client.post(
f"/mcp/tools/{tool}",
json={"version": chosen, "payload": payload},
headers={"Authorization": f"Bearer {HOLYSHEEP_KEY}"},
timeout=2.0,
)
response.raise_for_status()
return response.json()
def report(self) -> Dict[str, int]:
return dict(self._metrics)
Exemple d'usage en production
async def main() -> None:
router = MCPRouter()
result = await router.call_tool(
tool="lookup_inventory",
version="1.2.0",
payload={"sku": "TSHIRT-RED-XL"}
)
print(result)
asyncio.run(main())