écrits/tutorial/2026/06
Tutorial24 juin 2026·25 min

Construire des serveurs MCP de production avec FastMCP en Python

Apprenez à construire, tester et déployer des serveurs Model Context Protocol prêts pour la production en Python avec FastMCP 3.x. Couvre les outils, ressources, prompts, authentification et déploiement Docker.

Introduction

Le Model Context Protocol (MCP) est devenu le connecteur standard entre les assistants IA et les outils externes, sources de données et services. FastMCP — désormais en version 3.2 — propulse environ 70 % des serveurs MCP en production, tous langages confondus.

Si vous devez exposer une base de données, une API interne, un système de fichiers ou toute logique métier à Claude Desktop, Claude Code, Cursor ou Cline, FastMCP est la voie la plus rapide. Son API basée sur des décorateurs rappelle Flask et FastAPI, permettant aux développeurs Python de lancer un serveur en quelques minutes.

Dans ce guide, vous construirez un serveur MCP complet comprenant outils, ressources, templates de prompts, injection de dépendances, authentification par token Bearer et déploiement Docker — tout ce qu'il faut pour passer en production.

Prérequis

  • Python 3.10 ou supérieur (3.12+ recommandé pour les meilleures performances)
  • Gestionnaire de paquets UV (ou pip)
  • Une version récente de Claude Desktop, Cursor ou Cline pour les tests locaux
  • Notions de base de Python asynchrone

Ce que vous allez construire

Un Serveur d'intelligence projets — un serveur MCP qui donne à tout assistant IA accès à :

  • Un outil search_projects pour interroger une base de projets en mémoire
  • Un outil get_project_status pour récupérer des métadonnées de projet en temps réel
  • Une ressource projects://list exposant un instantané statique de tous les projets
  • Un template de prompt status_report guidant le résumé des données
  • Un transport HTTP avec authentification Bearer Token pour la production

Étape 1 : Configuration du projet

Créez un nouveau projet avec UV et installez FastMCP :

uv init project-mcp-server
cd project-mcp-server
uv add fastmcp

Ou avec pip :

mkdir project-mcp-server && cd project-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install fastmcp

Vérifiez l'installation :

fastmcp version
# FastMCP 3.2.4

Créez le fichier serveur principal :

touch server.py

Étape 2 : Initialisation du serveur

Ouvrez server.py et configurez l'instance FastMCP :

from fastmcp import FastMCP, Context
 
mcp = FastMCP(
    name="Project Intelligence Server",
    instructions=(
        "Vous avez accès à une base de données de projets. "
        "Utilisez search_projects pour chercher par mot-clé, "
        "et get_project_status pour récupérer les métadonnées en direct."
    ),
)

Le paramètre instructions est envoyé à l'IA en début de session pour décrire les capacités de votre serveur.

Étape 3 : Définir les outils

Les outils sont les primitives MCP les plus courantes — des fonctions appelables que l'IA invoque pour agir ou récupérer des données calculées.

FastMCP infère automatiquement le schéma des entrées à partir des annotations de type et des docstrings :

PROJECTS = [
    {"id": "p1", "name": "Plateforme Noqta", "status": "active", "lang": "TypeScript"},
    {"id": "p2", "name": "API Facturation", "status": "active", "lang": "Python"},
    {"id": "p3", "name": "Application Mobile", "status": "paused", "lang": "React Native"},
    {"id": "p4", "name": "Pipeline de données", "status": "archived", "lang": "Python"},
]
 
 
@mcp.tool
def search_projects(keyword: str, status: str = "all") -> list[dict]:
    """
    Recherche des projets par mot-clé et filtre optionnel de statut.
 
    Args:
        keyword: Terme de recherche correspondant au nom ou au langage du projet.
        status: Filtrer par statut : active, paused, archived ou all.
    """
    results = PROJECTS
    if status != "all":
        results = [p for p in results if p["status"] == status]
    keyword = keyword.lower()
    return [p for p in results if keyword in p["name"].lower() or keyword in p["lang"].lower()]
 
 
@mcp.tool
async def get_project_status(project_id: str, ctx: Context) -> dict:
    """
    Récupère le statut détaillé d'un projet par son identifiant.
 
    Args:
        project_id: Identifiant unique du projet (ex. p1, p2).
    """
    await ctx.info(f"Récupération du statut pour le projet {project_id}")
 
    project = next((p for p in PROJECTS if p["id"] == project_id), None)
    if project is None:
        raise ValueError(f"Projet '{project_id}' introuvable")
 
    return {
        **project,
        "last_commit": "2026-06-24",
        "open_issues": 3,
        "coverage": "87%",
    }

Points clés :

  • Les fonctions synchrones conviennent aux tâches CPU ; async def est nécessaire pour les E/S.
  • Le paramètre ctx: Context est injecté automatiquement — l'IA ne le passe pas manuellement.
  • ctx.info(), ctx.debug(), ctx.warning() émettent des messages de log affichés par le client.
  • Lever une exception Python standard produit un message d'erreur clair pour l'IA.

Étape 4 : Définir les ressources

Les ressources exposent des données en lecture plutôt que calculées. Elles sont idéales pour la configuration, la documentation ou les données statiques :

import json
 
 
@mcp.resource("projects://list")
def list_all_projects() -> str:
    """Instantané de tous les projets du système."""
    return json.dumps(PROJECTS, ensure_ascii=False, indent=2)
 
 
@mcp.resource("projects://{project_id}/details")
def project_details(project_id: str) -> str:
    """Vue détaillée d'un projet par son identifiant."""
    project = next((p for p in PROJECTS if p["id"] == project_id), None)
    if project is None:
        return json.dumps({"error": f"Projet {project_id} introuvable"}, ensure_ascii=False)
    return json.dumps(project, ensure_ascii=False, indent=2)

Les templates d'URI avec {placeholder} sont automatiquement mappés aux paramètres de la fonction.

Étape 5 : Définir les templates de prompts

Les prompts sont des instructions réutilisables qui guident le raisonnement de l'IA. Ils apparaissent dans les interfaces clients comme templates sélectionnables :

@mcp.prompt
def status_report(project_id: str, audience: str = "engineering") -> str:
    """
    Génère un template de rapport d'état pour un projet et un public cibles.
 
    Args:
        project_id: Identifiant du projet cible.
        audience: Public visé : engineering, management ou client.
    """
    tone = {
        "engineering": "technique, axé sur les métriques et les blocages",
        "management": "concis, axé sur le calendrier et les risques",
        "client": "non technique, positif, axé sur la valeur livrée",
    }.get(audience, "équilibré")
 
    return (
        f"À partir des données du projet '{project_id}', rédigez un rapport d'état {tone}. "
        "Incluez : statut actuel, progrès récents, problèmes ouverts et prochaines étapes. "
        "Maximum 300 mots."
    )

Étape 6 : Injection de dépendances avec Depends

Quand plusieurs outils partagent une ressource coûteuse — connexion DB, client HTTP, cache — utilisez Depends pour l'initialiser une seule fois par requête :

from contextlib import asynccontextmanager
from fastmcp.dependencies import Depends
 
 
async def get_db():
    """Simule un client DB. En production, remplacez par une vraie connexion."""
    class FakeDB:
        async def query(self, sql: str) -> list:
            return [{"result": f"Exécuté : {sql}"}]
 
    db = FakeDB()
    yield db
 
 
@mcp.tool
async def run_query(sql: str, db=Depends(get_db)) -> list:
    """
    Exécute une requête SQL en lecture seule sur la base de projets.
 
    Args:
        sql: Une instruction SELECT à exécuter.
    """
    return await db.query(sql)

Étape 7 : État au niveau du serveur avec lifespan

Pour un état devant persister toute la durée du serveur (pool de connexions, modèle chargé), utilisez l'API lifespan :

from fastmcp.server.lifespan import lifespan
 
 
@lifespan
async def app_lifespan(server):
    """Initialise et nettoie les ressources côté serveur."""
    print("Démarrage — construction de l'index projets...")
    index = {p["id"]: p for p in PROJECTS}
    yield {"index": index}
    print("Arrêt — libération de l'index.")
 
 
mcp_with_lifespan = FastMCP(
    name="Project Intelligence Server",
    lifespan=app_lifespan,
)
 
 
@mcp_with_lifespan.tool
def fast_lookup(project_id: str, ctx: Context) -> dict:
    """Recherche instantanée via l'index pré-construit."""
    index = ctx.lifespan_context["index"]
    return index.get(project_id, {"error": "introuvable"})

Étape 8 : Tests avec MCP Inspector

FastMCP intègre un support natif pour MCP Inspector — une interface de test dans le navigateur :

fastmcp dev inspector server.py

Cette commande lance un serveur local avec rechargement automatique et ouvre Inspector sur http://localhost:6274. Vous pouvez :

  • Parcourir les outils, ressources et prompts enregistrés
  • Appeler des outils directement avec des arguments personnalisés
  • Inspecter le cycle requête/réponse complet

Étape 9 : Connexion aux clients IA

Claude Desktop

Ajoutez votre serveur dans ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) :

{
  "mcpServers": {
    "project-intelligence": {
      "command": "fastmcp",
      "args": ["run", "/chemin/absolu/vers/server.py"]
    }
  }
}

Redémarrez Claude Desktop. Vos outils apparaîtront dans la liste des outils disponibles.

Claude Code

claude mcp add project-intelligence fastmcp run /chemin/absolu/vers/server.py

Cursor / Cline

Dans les paramètres Cursor → MCP Servers :

{
  "name": "project-intelligence",
  "command": "fastmcp run /chemin/absolu/vers/server.py"
}

Étape 10 : Transport HTTP avec authentification

Pour un déploiement distant ou un serveur partagé en équipe, passez au transport HTTP avec authentification Bearer Token :

from fastmcp.server.auth import StaticTokenVerifier
import os
 
auth = StaticTokenVerifier(
    tokens={
        os.environ["MCP_TOKEN_PROD"]: {"client_id": "prod", "scopes": ["read", "write"]},
        os.environ["MCP_TOKEN_READONLY"]: {"client_id": "viewer", "scopes": ["read"]},
    },
    required_scopes=["read"],
)
 
secured_mcp = FastMCP(
    name="Project Intelligence Server",
    auth=auth,
)
 
if __name__ == "__main__":
    secured_mcp.run(transport="http", host="0.0.0.0", port=8000)

Lancez le serveur sécurisé :

MCP_TOKEN_PROD=votre-token-secret \
MCP_TOKEN_READONLY=votre-token-lecture \
python server.py

Connectez un client avec le token :

uvx fastmcp-remote https://votre-serveur.example.com/mcp \
  --header "Authorization: Bearer votre-token-secret"

Étape 11 : Déploiement Docker

Créez un Dockerfile :

FROM python:3.12-slim
 
WORKDIR /app
 
RUN pip install uv
 
COPY pyproject.toml uv.lock* ./
RUN uv sync --frozen --no-dev
 
COPY server.py .
 
EXPOSE 8000
 
CMD ["uv", "run", "fastmcp", "run", "server.py", \
     "--transport", "http", "--host", "0.0.0.0", "--port", "8000"]

Construisez et lancez :

docker build -t project-mcp-server .
docker run -p 8000:8000 \
  -e MCP_TOKEN_PROD=votre-token-secret \
  project-mcp-server

Pour les équipes utilisant Docker Compose :

services:
  mcp-server:
    build: ./mcp
    ports:
      - "8000:8000"
    environment:
      MCP_TOKEN_PROD: ${MCP_TOKEN_PROD}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Étape 12 : Traçage avec OpenTelemetry

FastMCP 3.x inclut un support OpenTelemetry natif pour l'observabilité en production :

from fastmcp.telemetry import get_tracer
 
 
@mcp.tool
async def search_projects_traced(keyword: str, ctx: Context) -> list[dict]:
    """Recherche de projets avec traçage distribué."""
    tracer = get_tracer()
 
    with tracer.start_as_current_span("search.filter") as span:
        span.set_attribute("search.keyword", keyword)
        results = [p for p in PROJECTS if keyword.lower() in p["name"].lower()]
        span.set_attribute("search.result_count", len(results))
 
    await ctx.info(f"Trouvé {len(results)} projet(s) pour '{keyword}'")
    return results

Envoyez les traces vers Grafana, Jaeger ou Datadog via des variables d'environnement :

OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \
OTEL_SERVICE_NAME=project-mcp-server \
python server.py

Résolution des problèmes courants

L'outil n'apparaît pas dans Claude Desktop

  • Vérifiez que le chemin dans claude_desktop_config.json est absolu, non relatif.
  • Assurez-vous que fastmcp est dans le PATH utilisé par Claude Desktop.
  • Redémarrez complètement Claude Desktop après toute modification de config.

TypeError: cannot unpack non-iterable NoneType object

Cela signifie généralement que le générateur de dépendance a retourné None au lieu de yield. Assurez-vous que votre fonction Depends utilise yield, pas return.

Erreurs d'authentification sur transport HTTP

  • Vérifiez que le header Authorization: Bearer <token> est présent et correctement formaté.
  • Confirmez que required_scopes correspond aux scopes assignés au token dans StaticTokenVerifier.

Erreurs de syntaxe sur Python 3.10 avec les annotations de type

Ajoutez from __future__ import annotations en tête de fichier, ou passez à Python 3.12 où la syntaxe X | Y est pleinement supportée.

Prochaines étapes

  • Explorez l'intégration OpenAPI de FastMCP pour générer automatiquement un serveur MCP depuis une spec REST existante.
  • Ajoutez une limitation de débit (rate limiting) via un compteur Redis dans une dépendance Depends.
  • Connectez votre serveur à LangGraph ou PydanticAI via FastMCPToolset pour des workflows multi-agents.
  • Consultez le tutoriel Construire un serveur MCP en TypeScript pour l'équivalent côté JS.

Conclusion

FastMCP 3.x offre aux développeurs Python un chemin clair et production-ready vers l'écosystème MCP. Avec des décorateurs pour les outils, ressources et prompts, l'injection de dépendances, l'authentification Bearer Token et un transport HTTP prêt pour Docker, vous pouvez déployer un vrai serveur MCP en quelques heures. Ce même serveur se connecte à Claude Desktop, Claude Code, Cursor et Cline sans aucun code spécifique au client — une seule implémentation pour tous les assistants IA.