Déployer un Serveur MCP en Production
Mis à jour le 29 juillet 2026
Du prototype au serveur de production
Votre serveur tourne en local et ngrok l’expose au monde le temps d’une démonstration. Le problème apparaît dès que quelqu’un d’autre veut s’en servir : vous fermez votre terminal, l’URL meurt, et tous les connecteurs configurés chez vos collègues tombent en panne. Passer en production, c’est essentiellement obtenir une adresse stable, disponible en permanence, servie en HTTPS. Quatre options y mènent, de la plus immédiate à la plus maîtrisée, et le choix dépend surtout de la sensibilité de vos données et du contrôle que vous voulez garder sur l’infrastructure.
Option 1 : Hugging Face Spaces
Hugging Face Spaces met à disposition des machines virtuelles gratuites, sans GPU, largement suffisantes pour un serveur MCP qui relaie des appels d’API. C’est la voie la plus rapide vers un premier déploiement réel : trois fichiers et un git push.
mon-mcp-space/
├── app.py # Serveur FastAPI + MCP
├── requirements.txt # Dépendances
└── README.md # Metadata du Space
Le fichier app.py reprend exactement la structure vue jusqu’ici, à ceci près que les valeurs sensibles sont lues dans l’environnement — les Secrets HF les y injectent :
# app.py
"""Serveur MCP déployé sur Hugging Face Spaces."""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Mon Serveur Production")
@mcp.tool()
def hello(name: str) -> str:
"""Salue une personne.
Args:
name: Le prénom de la personne
Returns:
Message de salutation
"""
return f"Bonjour {name} depuis Hugging Face Spaces !"
@asynccontextmanager
async def lifespan(app: FastAPI):
async with mcp.session_manager.run():
yield
api = FastAPI(lifespan=lifespan)
api.mount("/mcp-endpoint", mcp.streamable_http_app())
# Pour les variables sensibles, utilisez les Secrets HF
API_KEY = os.environ.get("MY_API_KEY", "")
La particularité de la plateforme tient au README, dont l’en-tête YAML pilote la construction du Space. Le champ app_port doit correspondre au port réellement écouté par votre serveur, faute de quoi le déploiement réussit mais reste inaccessible :
---
title: Mon Serveur MCP
emoji: 🔧
colorFrom: blue
colorTo: green
sdk: docker
app_port: 7860
---
# Créer un Space sur huggingface.co
# Pousser le code
git push
# Le Space se déploie automatiquement
Votre endpoint devient alors https://votre-username-mon-mcp-space.hf.space/mcp-endpoint/mcp, en HTTPS et sans configuration supplémentaire.
Option 2 : Docker
Dès que le serveur touche à des données internes, l’hébergement sur votre propre infrastructure redevient la règle, et Docker en est le format standard. Le Dockerfile tient en quelques instructions, avec l’installation des dépendances placée avant la copie du code pour profiter du cache de couches lors des rebuilds :
FROM python:3.12-slim
WORKDIR /app
# Installer les dépendances
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copier le code
COPY . .
# Exposer le port
EXPOSE 8000
# Lancer le serveur
CMD ["uvicorn", "server:api", "--host", "0.0.0.0", "--port", "8000"]
fastapi>=0.115.0
uvicorn>=0.30.0
mcp>=1.0.0
fastmcp>=2.0.0
httpx>=0.27.0
Au lancement, l’option --restart unless-stopped est celle qui distingue un déploiement de production d’un simple essai : sans elle, le premier crash met votre serveur hors ligne jusqu’à ce que quelqu’un s’en aperçoive.
# Build l'image
docker build -t mon-mcp-server .
# Lancer le conteneur
docker run -d \
--name mcp-server \
-p 8000:8000 \
-e MY_API_KEY="secret" \
-e DATABASE_URL="postgresql://..." \
--restart unless-stopped \
mon-mcp-server
Docker Compose rend cette configuration reproductible et y ajoute un health check, que l’orchestrateur utilisera pour détecter un serveur figé — un processus vivant mais qui ne répond plus est le scénario que le simple redémarrage automatique ne couvre pas :
# docker-compose.yml
version: "3.8"
services:
mcp-server:
build: .
ports:
- "8000:8000"
environment:
- MY_API_KEY=${MY_API_KEY}
- DATABASE_URL=${DATABASE_URL}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/"]
interval: 30s
retries: 3
Options 3 et 4 : déploiements managés
FastMCP Cloud supprime toute l’étape d’infrastructure en déployant directement depuis un dépôt GitHub, et génère l’URL publique pour vous :
# Installer le CLI FastMCP
pip install fastmcp
# Déployer depuis GitHub
fastmcp deploy --repo https://github.com/user/mon-mcp-server
Cloudflare Workers vise un autre profil d’usage : le serverless, avec une exécution au plus près de l’utilisateur et donc une latence réduite. Un template dédié amorce le projet :
# Utiliser le template MCP de Cloudflare
npm create cloudflare@latest -- mon-mcp --template=cloudflare/ai/demos/remote-mcp-server
Ces deux options vous dispensent de gérer les certificats et les redémarrages, au prix d’une dépendance à la plateforme et de contraintes d’exécution — un Worker serverless, par exemple, se marie mal avec un serveur stateful.
Sécuriser le déploiement
Le HTTPS n’est pas négociable : Le Chat refuse les endpoints en clair, et un token transitant en HTTP est un token compromis. Trois chemins y mènent selon votre hébergement. Un reverse proxy Nginx ou Caddy placé devant votre serveur, avec un certificat Let’s Encrypt, couvre le cas du serveur dédié. Les plateformes cloud natives — Hugging Face, Cloudflare et la plupart des PaaS — fournissent le HTTPS automatiquement. Un tunnel, Cloudflare Tunnel par exemple, expose un serveur resté sur votre réseau interne sans ouvrir de port.
Aucun secret ne doit figurer dans le code. Notez la distinction entre les deux syntaxes ci-dessous : os.environ["MCP_API_TOKEN"] fait échouer le démarrage si la variable manque, ce qui est exactement le comportement souhaité pour un secret obligatoire, tandis que os.environ.get() tolère l’absence pour les valeurs optionnelles.
import os
# Configuration via variables d'environnement
API_TOKEN = os.environ["MCP_API_TOKEN"] # Obligatoire
DATABASE_URL = os.environ.get("DATABASE_URL") # Optionnel
DEBUG = os.environ.get("DEBUG", "false") == "true"
Un serveur public sans limitation de débit finit tôt ou tard saturé, volontairement ou non. Un limiteur par adresse IP suffit dans la plupart des cas :
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
limiter = Limiter(key_func=lambda request: request.client.host)
api = FastAPI()
api.state.limiter = limiter
@api.middleware("http")
async def rate_limit(request: Request, call_next):
# 100 requêtes par minute par IP
...
Voir ce qui se passe
En production, vous n’avez plus votre terminal sous les yeux, et un tool qui échoue une fois sur dix ne se diagnostique qu’avec des traces. Instrumentez au minimum l’entrée, la sortie et la durée de chaque appel :
import logging
import time
logger = logging.getLogger("mcp-server")
@mcp.tool()
async def my_tool(param: str) -> str:
"""Mon tool avec logging."""
start = time.time()
logger.info(f"Tool appelé avec param={param}")
try:
result = await process(param)
duration = time.time() - start
logger.info(f"Tool terminé en {duration:.2f}s")
return result
except Exception as e:
logger.error(f"Erreur dans le tool : {e}")
raise
La mesure de durée n’est pas un luxe : un tool qui met douze secondes à répondre déclenche des timeouts côté client et donne l’impression d’un serveur cassé alors qu’il fonctionne. Avant d’annoncer votre serveur, passez en revue les six points de configuration suivants.
- HTTPS actif et certificat valide
- Variables d’environnement pour tous les secrets
- Logs structurés activés
- Health check endpoint (
/ou/health) - Restart automatique en cas de crash
- Rate limiting configuré
Restent deux vérifications, qu’on saute le plus volontiers et qui pourtant révèlent les vrais problèmes : un test avec MCP Inspector lancé depuis l’extérieur, puis un test depuis Le Chat de Mistral. Un serveur qui répond parfaitement depuis votre machine peut être bloqué par un pare-feu ou une configuration CORS dès qu’on l’appelle d’ailleurs.
Points clés à retenir
- Hugging Face Spaces : le plus rapide, gratuit, idéal pour commencer
- Docker : contrôle total, adapté aux serveurs dédiés et au cloud
- FastMCP Cloud : déploiement simplifié depuis GitHub
- Cloudflare Workers : serverless, faible latence, scalable
- Sécurisez avec HTTPS, variables d’environnement, et rate limiting
- Ajoutez du monitoring et des logs pour le debugging en production