Aller au contenu principal

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