Créer un Serveur MCP en Python avec FastMCP 3.0 : Le Guide Pratique

Apprenez à construire et déployer un serveur MCP en Python avec FastMCP 3.0. Du premier outil à la production : authentification OAuth, observabilité OpenTelemetry, Providers, Transforms et intégration Claude Desktop.

Introduction : Pourquoi le Model Context Protocol Change la Donne en 2026

Vous avez probablement déjà construit un agent IA capable de répondre à des questions, de résumer du texte ou de générer du code. Jusque-là, rien de bien compliqué. Mais dès qu'il s'agit de lui donner accès à vos fichiers locaux, votre base de données, ou une API tierce, les choses se compliquent très vite. Chaque intégration nécessite son propre connecteur, sa propre logique, ses propres tests. Et quand vous voulez connecter N outils à M applications IA, vous vous retrouvez avec N×M connecteurs à maintenir.

C'est exactement ce problème que le Model Context Protocol (MCP) résout.

Lancé par Anthropic en novembre 2024, MCP est devenu en 2026 le standard de facto pour connecter les LLM aux outils externes. On le compare souvent à l'USB-C de l'intelligence artificielle — et honnêtement, l'analogie tient plutôt bien. Un protocole unique et ouvert qui permet à n'importe quelle application IA de se connecter à n'importe quel outil, sans connecteur dédié. Le problème N×M devient N+M : chaque outil expose un seul serveur MCP, chaque application IA embarque un seul client MCP.

Le protocole compte aujourd'hui plus de 1 200 serveurs publics et 97 millions de téléchargements SDK mensuels. OpenAI, Google DeepMind et Microsoft l'ont adopté. En décembre 2025, Anthropic a transféré la gouvernance à l'Agentic AI Foundation sous la Linux Foundation.

Dans ce guide, on va construire un serveur MCP complet en Python avec FastMCP 3.0 — le framework derrière 70 % des serveurs MCP existants. On part de zéro, et on va jusqu'au déploiement en production avec authentification, observabilité et bonnes pratiques de sécurité. Allez, on s'y met.

Comprendre l'Architecture MCP

Le Modèle Client-Serveur

MCP repose sur une architecture client-serveur assez classique. Le client MCP est intégré dans l'application hôte (Claude Desktop, Cursor, Claude Code, ou votre propre agent) et communique avec un ou plusieurs serveurs MCP qui exposent des fonctionnalités spécifiques.

Concrètement, voici comment ça se passe :

  1. L'utilisateur envoie une requête au LLM via l'application hôte.
  2. Le LLM identifie qu'il a besoin d'un outil externe et formule un appel structuré.
  3. Le client MCP transmet la requête au serveur MCP approprié.
  4. Le serveur exécute l'action et retourne le résultat.
  5. Le LLM intègre le résultat dans sa réponse finale.

Rien de révolutionnaire dans le concept, mais c'est justement la standardisation qui fait toute la force du protocole.

Les Trois Primitives MCP

Tout serveur MCP expose ses fonctionnalités via trois primitives fondamentales. C'est important de bien les distinguer, parce que chacune répond à un besoin différent :

  • Tools (Outils) — Ce sont des fonctions que le LLM peut invoquer pour effectuer des actions. C'est la primitive la plus utilisée, et de loin. Le LLM décide quand et comment les appeler en fonction du contexte de la conversation.
  • Resources (Ressources) — Des données en lecture seule que le client peut consulter. Pensez-y comme des endpoints GET dans une API REST : contenus de fichiers, résultats de requêtes, configurations.
  • Prompts — Des templates pré-définis qui guident le LLM pour des tâches spécifiques. Ils fournissent des instructions structurées pour les workflows récurrents.

Les Modes de Transport

MCP supporte deux modes de transport principaux :

  • stdio — Communication via l'entrée/sortie standard. Le client lance le serveur comme un processus enfant et échange des messages via stdin/stdout. Idéal pour les intégrations locales.
  • Streamable HTTP — Communication via HTTP avec support du streaming. C'est le mode recommandé pour les déploiements en production et les serveurs distants. Il remplace l'ancien transport SSE depuis mars 2025.

Installer FastMCP 3.0 et Configurer l'Environnement

FastMCP 3.0 est sorti en version stable le 18 février 2026, et c'est une refonte architecturale majeure. La bonne nouvelle ? La surface API reste largement compatible — @mcp.tool() fonctionne exactement comme avant. Ce qui a changé, ce sont les fondations, entièrement repensées autour des concepts de Providers et Transforms (on y reviendra plus loin).

Prérequis

  • Python 3.10 ou supérieur
  • Un gestionnaire de paquets (uv recommandé, ou pip)
  • Un client MCP pour tester (Claude Desktop, Cursor, ou MCP Inspector)

Installation avec uv

# Créer un nouveau projet
uv init mon-serveur-mcp
cd mon-serveur-mcp

# Créer et activer l'environnement virtuel
uv venv
source .venv/bin/activate

# Installer FastMCP 3.0
uv add fastmcp

# Vérifier l'installation
python -c "import fastmcp; print(fastmcp.__version__)"

Installation avec pip

# Alternative avec pip
python -m venv .venv
source .venv/bin/activate
pip install "fastmcp>=3.0"

Créer Votre Premier Serveur MCP

Un Serveur Minimal avec @mcp.tool

Commençons par un serveur qui expose un seul outil. Avec FastMCP, c'est presque trop simple :

from fastmcp import FastMCP

# Initialiser le serveur avec un nom descriptif
mcp = FastMCP("Calculatrice")

@mcp.tool
def additionner(a: float, b: float) -> float:
    """Additionne deux nombres et retourne le résultat."""
    return a + b

@mcp.tool
def multiplier(a: float, b: float) -> float:
    """Multiplie deux nombres et retourne le résultat."""
    return a * b

if __name__ == "__main__":
    mcp.run()

C'est tout. Vraiment. FastMCP utilise automatiquement les annotations de type Python et les docstrings pour générer le schéma JSON de chaque outil. Le LLM reçoit ces schémas et sait immédiatement quels outils sont disponibles et comment les utiliser.

Un truc que j'apprécie particulièrement avec FastMCP 3.0 : vos fonctions décorées restent des fonctions Python tout à fait normales. Vous pouvez les importer, les appeler directement et les tester unitairement sans aucune infrastructure MCP. Pas besoin de mocker quoi que ce soit.

Ajouter des Ressources

Les ressources permettent d'exposer des données que le LLM peut consulter. Elles sont identifiées par des URIs :

from fastmcp import FastMCP

mcp = FastMCP("Documentation Interne")

@mcp.resource("config://app/settings")
def obtenir_configuration() -> dict:
    """Retourne la configuration actuelle de l'application."""
    return {
        "version": "2.4.1",
        "environnement": "production",
        "base_de_donnees": "PostgreSQL 16",
        "cache": "Redis 7.2"
    }

@mcp.resource("docs://api/{endpoint}")
def obtenir_doc_endpoint(endpoint: str) -> str:
    """Retourne la documentation d'un endpoint API spécifique."""
    docs = {
        "users": "GET /api/users - Liste tous les utilisateurs. Supporte la pagination via ?page=&limit=",
        "orders": "GET /api/orders - Liste les commandes. Filtrage par ?status=pending|completed|cancelled",
    }
    return docs.get(endpoint, f"Aucune documentation trouvée pour l'endpoint '{endpoint}'.")

if __name__ == "__main__":
    mcp.run()

Définir des Prompts Réutilisables

Les prompts MCP sont des templates que le client peut proposer à l'utilisateur pour des tâches récurrentes. En pratique, c'est un peu comme créer des raccourcis pour des instructions complexes :

from fastmcp import FastMCP

mcp = FastMCP("Assistant Code Review")

@mcp.prompt
def revue_de_code(langage: str, fichier: str) -> str:
    """Génère un prompt structuré pour une revue de code."""
    return f"""Effectue une revue de code approfondie du fichier {fichier} écrit en {langage}.

Analyse les points suivants :
1. Lisibilité et conventions de nommage
2. Gestion des erreurs et cas limites
3. Performance et complexité algorithmique
4. Vulnérabilités de sécurité potentielles
5. Couverture de tests suggérée

Fournis des suggestions concrètes avec des exemples de code corrigé."""

@mcp.prompt
def generer_tests(fonction: str, langage: str = "python") -> str:
    """Génère un prompt pour créer des tests unitaires."""
    return f"""Écris des tests unitaires complets pour la fonction suivante en {langage} :

{fonction}

Inclus des tests pour :
- Le cas nominal (happy path)
- Les cas limites (valeurs nulles, listes vides, nombres négatifs)
- Les cas d'erreur (entrées invalides, exceptions attendues)
- Les cas de performance si pertinent"""

if __name__ == "__main__":
    mcp.run()

Construire un Serveur MCP Complet : Gestionnaire de Tâches

Bon, passons aux choses sérieuses. On va construire un serveur MCP qui gère un système de tâches avec persistance SQLite, opérations CRUD complètes et requêtes asynchrones. C'est le genre de projet que vous pourriez réellement utiliser au quotidien.

import aiosqlite
import json
from datetime import datetime, timezone
from pathlib import Path
from fastmcp import FastMCP, Context

# Configuration du serveur
DB_PATH = Path("tasks.db")
mcp = FastMCP(
    "Gestionnaire de Tâches",
    json_response=True
)


async def initialiser_db():
    """Crée la base de données et la table si elles n'existent pas."""
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute("""
            CREATE TABLE IF NOT EXISTS tasks (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                titre TEXT NOT NULL,
                description TEXT DEFAULT '',
                statut TEXT DEFAULT 'en_attente'
                    CHECK(statut IN ('en_attente', 'en_cours', 'terminee', 'annulee')),
                priorite INTEGER DEFAULT 2
                    CHECK(priorite BETWEEN 1 AND 5),
                creee_le TEXT NOT NULL,
                mise_a_jour_le TEXT NOT NULL
            )
        """)
        await db.commit()


@mcp.tool
async def creer_tache(
    titre: str,
    description: str = "",
    priorite: int = 2
) -> dict:
    """Crée une nouvelle tâche dans le gestionnaire.

    Args:
        titre: Le titre de la tâche (obligatoire)
        description: Description détaillée de la tâche
        priorite: Niveau de priorité de 1 (basse) à 5 (critique)
    """
    await initialiser_db()
    maintenant = datetime.now(timezone.utc).isoformat()

    async with aiosqlite.connect(DB_PATH) as db:
        cursor = await db.execute(
            """INSERT INTO tasks (titre, description, priorite, creee_le, mise_a_jour_le)
               VALUES (?, ?, ?, ?, ?)""",
            (titre, description, priorite, maintenant, maintenant)
        )
        await db.commit()
        return {
            "id": cursor.lastrowid,
            "titre": titre,
            "statut": "en_attente",
            "priorite": priorite,
            "message": f"Tâche '{titre}' créée avec succès."
        }


@mcp.tool
async def lister_taches(
    statut: str | None = None,
    priorite_min: int | None = None
) -> list[dict]:
    """Liste les tâches avec filtrage optionnel.

    Args:
        statut: Filtrer par statut (en_attente, en_cours, terminee, annulee)
        priorite_min: Afficher uniquement les tâches avec cette priorité minimum
    """
    await initialiser_db()
    query = "SELECT * FROM tasks WHERE 1=1"
    params = []

    if statut:
        query += " AND statut = ?"
        params.append(statut)
    if priorite_min is not None:
        query += " AND priorite >= ?"
        params.append(priorite_min)

    query += " ORDER BY priorite DESC, creee_le DESC"

    async with aiosqlite.connect(DB_PATH) as db:
        db.row_factory = aiosqlite.Row
        async with db.execute(query, params) as cursor:
            rows = await cursor.fetchall()
            return [dict(row) for row in rows]


@mcp.tool
async def mettre_a_jour_statut(id_tache: int, nouveau_statut: str) -> dict:
    """Met à jour le statut d'une tâche existante.

    Args:
        id_tache: L'identifiant de la tâche à modifier
        nouveau_statut: Le nouveau statut (en_attente, en_cours, terminee, annulee)
    """
    statuts_valides = {"en_attente", "en_cours", "terminee", "annulee"}
    if nouveau_statut not in statuts_valides:
        return {"erreur": f"Statut invalide. Valeurs acceptées : {statuts_valides}"}

    await initialiser_db()
    maintenant = datetime.now(timezone.utc).isoformat()

    async with aiosqlite.connect(DB_PATH) as db:
        cursor = await db.execute(
            "UPDATE tasks SET statut = ?, mise_a_jour_le = ? WHERE id = ?",
            (nouveau_statut, maintenant, id_tache)
        )
        await db.commit()

        if cursor.rowcount == 0:
            return {"erreur": f"Aucune tâche trouvée avec l'id {id_tache}."}

        return {
            "id": id_tache,
            "nouveau_statut": nouveau_statut,
            "message": f"Statut de la tâche {id_tache} mis à jour vers '{nouveau_statut}'."
        }


@mcp.tool
async def supprimer_tache(id_tache: int) -> dict:
    """Supprime définitivement une tâche du gestionnaire.

    Args:
        id_tache: L'identifiant de la tâche à supprimer
    """
    await initialiser_db()

    async with aiosqlite.connect(DB_PATH) as db:
        cursor = await db.execute(
            "DELETE FROM tasks WHERE id = ?",
            (id_tache,)
        )
        await db.commit()

        if cursor.rowcount == 0:
            return {"erreur": f"Aucune tâche trouvée avec l'id {id_tache}."}

        return {"message": f"Tâche {id_tache} supprimée avec succès."}


@mcp.resource("tasks://stats")
async def statistiques_taches() -> dict:
    """Retourne les statistiques globales du gestionnaire de tâches."""
    await initialiser_db()

    async with aiosqlite.connect(DB_PATH) as db:
        async with db.execute(
            "SELECT statut, COUNT(*) as total FROM tasks GROUP BY statut"
        ) as cursor:
            rows = await cursor.fetchall()
            stats = {row[0]: row[1] for row in rows}

        async with db.execute("SELECT COUNT(*) FROM tasks") as cursor:
            total = (await cursor.fetchone())[0]

    return {"total": total, "par_statut": stats}


if __name__ == "__main__":
    mcp.run()

Ce serveur illustre plusieurs bonnes pratiques qu'on retrouve dans à peu près tous les projets MCP bien conçus :

  • Fonctions asynchrones — Toutes les opérations d'entrée/sortie utilisent async/await pour éviter de bloquer le serveur pendant les requêtes base de données. C'est vraiment indispensable dès que vous gérez de l'I/O.
  • Validation des entrées — Les statuts sont vérifiés avant toute modification. Les contraintes SQL (CHECK) ajoutent une deuxième couche de sécurité.
  • Docstrings détaillées — Chaque fonction documente ses paramètres avec des exemples de valeurs acceptées. Le LLM s'en sert directement pour savoir comment appeler l'outil correctement.
  • Annotations de type — Les types Python (str, int, dict, list) servent à générer automatiquement le schéma JSON que le LLM consulte.

Tester et Déboguer avec MCP Inspector

FastMCP inclut un outil de débogage intégré — MCP Inspector — qui vous permet de tester chaque outil, ressource et prompt de manière interactive. C'est probablement l'outil que vous utiliserez le plus pendant le développement :

# Lancer le serveur en mode développement avec hot reload
fastmcp dev server.py

# L'Inspector s'ouvre automatiquement dans le navigateur
# Adresse par défaut : http://127.0.0.1:6274

L'Inspector offre trois onglets qui correspondent (sans surprise) aux trois primitives MCP :

  • Tools — Listez tous les outils disponibles, visualisez leur schéma JSON, et exécutez-les avec des paramètres de test.
  • Resources — Parcourez les URIs disponibles et consultez les données retournées.
  • Prompts — Testez les templates de prompt avec différentes valeurs de paramètres.

Pour les tests automatisés, FastMCP 3.0 permet d'appeler directement les fonctions décorées sans avoir besoin de monter toute l'infrastructure MCP. C'est un vrai gain de temps :

import pytest
import asyncio

# Vos fonctions restent des fonctions Python normales
from server import creer_tache, lister_taches, mettre_a_jour_statut

@pytest.mark.asyncio
async def test_creer_et_lister_taches():
    # Créer une tâche
    resultat = await creer_tache(
        titre="Écrire des tests",
        description="Couvrir les cas limites",
        priorite=3
    )
    assert resultat["titre"] == "Écrire des tests"
    assert resultat["statut"] == "en_attente"

    # Vérifier qu'elle apparaît dans la liste
    taches = await lister_taches()
    assert any(t["titre"] == "Écrire des tests" for t in taches)

@pytest.mark.asyncio
async def test_statut_invalide():
    resultat = await mettre_a_jour_statut(1, "invalide")
    assert "erreur" in resultat

Architecture Avancée : Providers et Transforms

C'est là que FastMCP 3.0 se distingue vraiment de la version précédente. Le framework introduit une séparation nette entre la provenance des composants (les Providers) et leur transformation (les Transforms). En pratique, ça rend le tout incroyablement composable.

FileSystemProvider : Découverte Automatique

Au lieu de déclarer tous vos outils dans un seul fichier (ce qui devient vite ingérable au-delà d'une dizaine d'outils), vous pouvez pointer un FileSystemProvider vers un répertoire et laisser FastMCP découvrir automatiquement vos outils — avec rechargement à chaud en bonus :

from fastmcp import FastMCP
from fastmcp.providers import FileSystemProvider

mcp = FastMCP("Multi-Outils")

# Tous les fichiers Python dans le répertoire tools/ sont
# automatiquement découverts et rechargés à la modification
mcp.add_provider(FileSystemProvider("./tools"))

if __name__ == "__main__":
    mcp.run()

Chaque fichier dans tools/ peut contenir des fonctions décorées avec @tool, @resource ou @prompt — elles seront automatiquement enregistrées au démarrage du serveur. Vous modifiez un fichier, le serveur recharge. Simple.

OpenAPIProvider : Wrapper des APIs REST

Vous avez déjà une API REST avec une spécification OpenAPI ? FastMCP peut l'exposer directement comme un serveur MCP, sans écrire une seule ligne de code métier :

from fastmcp import FastMCP
from fastmcp.providers import OpenAPIProvider

mcp = FastMCP("API Wrapper")

# Génère automatiquement des outils MCP depuis un spec OpenAPI
mcp.add_provider(
    OpenAPIProvider(
        "https://api.example.com/openapi.json",
        base_url="https://api.example.com",
        headers={"Authorization": "Bearer ${API_TOKEN}"}
    )
)

if __name__ == "__main__":
    mcp.run()

Franchement, c'est l'un des aspects les plus pratiques de FastMCP 3.0. Si vous avez des APIs existantes bien documentées, vous pouvez les rendre accessibles à vos agents IA en quelques minutes.

Transforms : Filtrer, Renommer et Sécuriser

Les Transforms fonctionnent comme des middlewares pour les Providers. Ils permettent de modifier le comportement des composants sans toucher au code source :

from fastmcp import FastMCP
from fastmcp.providers import FileSystemProvider
from fastmcp.transforms import NamespaceTransform, FilterTransform

mcp = FastMCP("Production Server")

# Charger les outils depuis un répertoire
provider = FileSystemProvider("./tools")

# Appliquer des transformations en chaîne
provider = NamespaceTransform("projets", provider)  # Préfixe tous les outils avec "projets_"
provider = FilterTransform(provider, tags=["public"])  # N'expose que les outils tagués "public"

mcp.add_provider(provider)

if __name__ == "__main__":
    mcp.run()

Cette composabilité est l'une des vraies forces de FastMCP 3.0. Vous pouvez enchaîner les Providers et les Transforms pour construire des architectures assez sophistiquées : proxyer un serveur distant, filtrer ses outils par tag, les renommer, et les exposer uniquement aux utilisateurs authentifiés. Chaque pièce est indépendante et testable.

Sécuriser Votre Serveur MCP en Production

La sécurité d'un serveur MCP, c'est un sujet qu'il ne faut vraiment pas prendre à la légère. Contrairement aux APIs traditionnelles, les serveurs MCP opèrent avec des permissions déléguées de l'utilisateur et permettent des chaînes d'appels d'outils — ce qui amplifie considérablement l'impact potentiel d'une faille. L'OWASP a d'ailleurs publié en 2026 un guide dédié à la sécurisation des serveurs MCP, ce qui en dit long sur l'importance du sujet.

Principe du Moindre Privilège

Chaque serveur MCP devrait avoir une responsabilité unique et bien définie. Évitez les serveurs monolithiques qui gèrent à la fois les fichiers, les bases de données et les emails. Divisez-les plutôt en services focalisés :

# Mauvaise pratique : un serveur monolithique
mcp_monolithique = FastMCP("Tout-en-un")  # Trop de responsabilités

# Bonne pratique : des serveurs spécialisés
mcp_fichiers = FastMCP("Gestionnaire Fichiers")
mcp_database = FastMCP("Accès Base de Données")
mcp_email = FastMCP("Service Email")

Validation Stricte des Entrées

Appliquez une validation rigoureuse sur tous les paramètres d'outils. Ne faites jamais confiance aux entrées provenant du LLM — elles pourraient être le résultat d'une injection de prompt (et oui, ça arrive plus souvent qu'on ne le pense) :

import re
from pathlib import Path

REPERTOIRE_AUTORISE = Path("/data/projets")

@mcp.tool
def lire_fichier(chemin: str) -> str:
    """Lit le contenu d'un fichier dans le répertoire projets.

    Args:
        chemin: Chemin relatif au répertoire projets
    """
    # Nettoyer et valider le chemin
    chemin_resolu = (REPERTOIRE_AUTORISE / chemin).resolve()

    # Vérifier la traversée de répertoire
    if not str(chemin_resolu).startswith(str(REPERTOIRE_AUTORISE)):
        return "Erreur : accès en dehors du répertoire autorisé refusé."

    # Vérifier les extensions autorisées
    extensions_ok = {".py", ".js", ".ts", ".md", ".txt", ".json", ".yaml"}
    if chemin_resolu.suffix not in extensions_ok:
        return f"Erreur : extension '{chemin_resolu.suffix}' non autorisée."

    if not chemin_resolu.exists():
        return f"Erreur : le fichier '{chemin}' n'existe pas."

    return chemin_resolu.read_text(encoding="utf-8")

Authentification OAuth 2.0

Pour les déploiements en production accessibles à distance, FastMCP 3.0 intègre un support natif de l'authentification OAuth 2.0 avec validation JWT :

from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider

auth = BearerAuthProvider(
    jwks_url="https://auth.example.com/.well-known/jwks.json",
    issuer="https://auth.example.com",
    audience="mcp-server-prod"
)

mcp = FastMCP(
    "Serveur Sécurisé",
    auth=auth
)

@mcp.tool
async def action_sensible(ctx: Context, donnees: str) -> dict:
    """Action qui nécessite une authentification."""
    # L'utilisateur est automatiquement authentifié
    user = ctx.user
    return {"resultat": f"Action exécutée par {user.name}"}

Déployer un Serveur MCP Distant

Transport Streamable HTTP

Pour rendre votre serveur accessible à distance, utilisez le transport Streamable HTTP — c'est le mode recommandé en production depuis 2025 :

from fastmcp import FastMCP

mcp = FastMCP(
    "API Distante",
    stateless_http=True,  # Scalabilité horizontale
    json_response=True     # Réponses JSON structurées
)

# ... vos outils, ressources et prompts ...

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8080)

Conteneurisation avec Docker

Voici un Dockerfile typique pour un serveur MCP en production. Rien d'extravagant, mais ça fait le travail :

FROM python:3.12-slim

WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen

COPY . .

EXPOSE 8080

CMD ["python", "server.py"]

Observabilité avec OpenTelemetry

FastMCP 3.0 intègre nativement OpenTelemetry, et c'est un vrai plus pour la production. Chaque appel d'outil, lecture de ressource et rendu de prompt est automatiquement tracé :

from fastmcp import FastMCP
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

# Configurer OpenTelemetry
provider = TracerProvider()
provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://jaeger:4317"))
)
trace.set_tracer_provider(provider)

# FastMCP instrumente automatiquement tous les appels
mcp = FastMCP("Serveur Observable")

@mcp.tool
async def operation_tracee(requete: str) -> dict:
    """Cette opération sera automatiquement tracée par OpenTelemetry."""
    # Les spans incluent : nom du composant, type de provider,
    # ID de session, et contexte d'authentification
    return {"resultat": f"Traitement de : {requete}"}

Chaque span inclut des attributs standardisés : clé du composant, type de provider, identifiant de session et contexte d'authentification. Côté client, les appels sortants sont encapsulés avec la propagation du contexte de trace W3C.

Connecter à Claude Desktop et Autres Clients

Pour connecter votre serveur local à Claude Desktop, il suffit de modifier le fichier de configuration :

// Sur macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
// Sur Windows : %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "gestionnaire-taches": {
      "command": "uv",
      "args": [
        "--directory", "/chemin/vers/mon-serveur-mcp",
        "run", "server.py"
      ]
    }
  }
}

Pour un serveur distant accessible via HTTP :

{
  "mcpServers": {
    "serveur-distant": {
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer votre-token-ici"
      }
    }
  }
}

Et si vous voulez aller encore plus vite, la CLI FastMCP permet d'enregistrer automatiquement un serveur dans Claude Desktop, Cursor ou Goose :

# Enregistrer dans Claude Desktop
fastmcp install server.py --client claude

# Enregistrer dans Cursor
fastmcp install server.py --client cursor

FAQ

Quelle est la différence entre MCP et le function calling classique des API LLM ?

Le function calling est une capacité des LLM qui leur permet de générer des appels structurés vers des fonctions pré-définies. MCP, lui, est un protocole de transport qui standardise la manière dont ces appels circulent entre un client IA et un serveur d'outils. Concrètement, MCP utilise le function calling comme mécanisme sous-jacent, mais ajoute la découverte dynamique d'outils, la gestion du cycle de vie des connexions, et un format d'échange universel compatible avec tous les LLM.

Peut-on utiliser MCP avec des LLM locaux ou seulement avec les API cloud ?

MCP fonctionne avec n'importe quel LLM — cloud ou local. Les modèles locaux exécutés via Ollama, llama.cpp ou vLLM peuvent être connectés à des serveurs MCP tant que votre application cliente implémente le protocole. La seule condition réelle, c'est que le modèle supporte le function calling ou le tool use.

Comment gérer les erreurs et les timeouts dans un serveur MCP en production ?

FastMCP 3.0 propose les tâches en arrière-plan via l'intégration Docket (SEP-1686 du protocole MCP). Pour les opérations longues, utilisez des files de tâches persistantes sauvegardées dans SQLite ou PostgreSQL. Pour la gestion des erreurs, la bonne pratique est de retourner des messages d'erreur structurés dans vos outils plutôt que de lever des exceptions — le LLM pourra ainsi interpréter le problème et proposer des alternatives à l'utilisateur.

Combien d'outils un serveur MCP peut-il exposer sans dégrader les performances ?

Techniquement, pas de limite protocolaire. Mais en pratique, des tests internes chez Anthropic montrent que 58 outils consomment environ 55 000 tokens en définitions. Au-delà de 30-50 outils, la capacité du LLM à sélectionner le bon outil diminue sensiblement. La bonne pratique est de diviser les outils en serveurs spécialisés et d'utiliser les Transforms de FastMCP (notamment les namespaces) pour organiser les groupes d'outils.

FastMCP est-il adapté à un environnement de production à fort trafic ?

Oui, à condition de bien le configurer. Utilisez le transport Streamable HTTP avec stateless_http=True pour la scalabilité horizontale. Conteneurisez avec Docker et déployez derrière un reverse proxy (Nginx ou Caddy) avec TLS. L'instrumentation OpenTelemetry native vous donne une visibilité complète sur la latence et les erreurs. Et pour les charges les plus lourdes, FastMCP 3.0 supporte les workers horizontaux via Docket, avec des files de tâches persistantes.

À propos de l'auteur Editorial Team

Our team of expert writers and editors.