Points essentiels

  • Le SDK MCP Python s'installe via pip install "mcp[cli]" et expose trois primitives : Tools (fonctions exécutables), Resources (données en lecture), Prompts (templates réutilisables)
  • Le transport stdio est destiné aux intégrations locales Claude Desktop et Cursor ; le transport SSE permet un déploiement distant accessible via HTTP
  • MCP Inspector (npx @modelcontextprotocol/inspector) est l'outil officiel de test sans LLM — indispensable avant la mise en production
  • La publication sur Smithery.ai nécessite un smithery.yaml et rend le serveur instantanément disponible pour tous les clients MCP compatibles

Le Model Context Protocol (MCP) s'est imposé en 2026 comme le standard de facto pour connecter les grands modèles de langage à des outils et sources de données externes. Là où les API REST exposent des endpoints HTTP statiques, MCP fournit un protocole dynamique de découverte des capacités : le LLM apprend en temps réel quels tools sont disponibles, comment les invoquer avec typage fort, et comment interpréter les résultats structurés. Créer un serveur MCP en Python, c'est donner à n'importe quel agent IA — Claude Desktop, Cursor, Windsurf — la capacité d'interagir avec vos systèmes internes : bases de données, APIs métier, systèmes de fichiers, services web propriétaires. Ce guide couvre l'ensemble du cycle de vie : installation du SDK, implémentation des trois primitives, tests avec MCP Inspector, intégration Claude Desktop et publication sur Smithery.

Prérequis : Python 3.10 minimum (3.12 recommandé), pip à jour, Node.js 18+ pour MCP Inspector. Le SDK MCP Python est un projet officiel maintenu sur github.com/modelcontextprotocol/python-sdk. Il implémente la spécification MCP 2025-11-05 (version stable).

Architecture MCP : hosts, clients et serveurs

Avant d'écrire la première ligne de code, comprendre les trois rôles de l'écosystème MCP est indispensable. Le host est l'application LLM côté utilisateur : Claude Desktop, Cursor, ou votre propre application. Le host gère le cycle de vie des connexions et prend les décisions finales sur quels tools invoquer. Le client MCP est un composant interne au host qui implémente le protocole côté consommateur : il établit la connexion, envoie les requêtes JSON-RPC et interprète les réponses. Le serveur MCP — ce que vous allez créer — expose des capabilities (tools, resources, prompts) via le protocole JSON-RPC 2.0 encapsulé dans MCP.

La communication suit un modèle request/response strict sur un canal bidirectionnel. Chaque tool call est indépendant, mais le serveur peut maintenir son propre état interne entre les requêtes d'une même session (connexion ouverte). Cette architecture simplifie le développement par rapport aux webhooks asynchrones classiques.

Cycle de vie d'une connexion MCP

1. Le host lance le processus serveur MCP (stdio) ou établit une connexion SSE (HTTP).
2. Le client envoie initialize avec sa version de protocole et ses capabilities.
3. Le serveur répond avec ses capabilities : liste des tools, resources, prompts disponibles.
4. Le LLM reçoit la liste des tools et peut les invoquer via tools/call.
5. Le serveur exécute la logique métier et retourne le résultat structuré.
6. La connexion se ferme à la fin de la session host (stdio) ou sur déconnexion client (SSE).

Installation du SDK officiel

Le SDK Python MCP est disponible sur PyPI sous le nom mcp. L'option [cli] ajoute les outils en ligne de commande pour le développement et la publication Smithery.

# Installation minimale
pip install mcp

# Installation complète avec CLI (recommandé pour le développement)
pip install "mcp[cli]"

# Vérification
python -c "import mcp; print(mcp.__version__)"
mcp --version

Pour un projet professionnel, créez un environnement virtuel dédié et un fichier pyproject.toml :

[project]
name = "mon-serveur-mcp"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = [
    "mcp[cli]>=1.0.0",
    "pydantic>=2.0",
    "httpx>=0.27",
]

Premier serveur MCP minimal avec FastMCP

Le SDK expose une interface de haut niveau basée sur des décorateurs via la classe FastMCP. Le fichier suivant crée un serveur MCP fonctionnel en moins de 25 lignes.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("calculatrice-mcp")

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

@mcp.tool()
def calculer_tva(prix_ht: float, taux: float = 20.0) -> dict:
    # Calcule la TVA et le prix TTC.
    # Args:
    #   prix_ht: Prix hors taxes en euros
    #   taux: Taux de TVA en pourcentage (défaut: 20.0%)
    tva = prix_ht * taux / 100
    return {
        "prix_ht": prix_ht,
        "tva": round(tva, 2),
        "prix_ttc": round(prix_ht + tva, 2),
        "taux_applique": taux
    }

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

Le décorateur @mcp.tool() transforme la fonction Python en tool MCP. La description fournie est ce que le LLM lira pour décider quand utiliser ce tool — elle doit être précise et concise. Les annotations de type Python sont converties en JSON Schema pour la validation automatique des paramètres côté client.

Les trois primitives MCP

Tools : fonctions exécutables

Les Tools sont la primitive principale. Ils peuvent modifier l'état du système (écrire en base, envoyer un email) ou retourner des données calculées. Le SDK gère la sérialisation des types Python vers JSON via Pydantic.

from pydantic import BaseModel
import httpx

class ResultatCVE(BaseModel):
    cve_id: str
    url: str
    description: str
    cvss_score: float | None = None

@mcp.tool()
async def rechercher_cve(terme: str, limite: int = 10) -> list[ResultatCVE]:
    # Recherche des CVE dans la base NVD par terme ou CVE-ID.
    # Args:
    #   terme: Terme de recherche ou CVE-ID exact
    #   limite: Nombre maximum de résultats (1 à 50)
    limite = max(1, min(limite, 50))
    async with httpx.AsyncClient(timeout=15.0) as client:
        resp = await client.get(
            "https://services.nvd.nist.gov/rest/json/cves/2.0",
            params={"keywordSearch": terme, "resultsPerPage": limite}
        )
        resp.raise_for_status()
        data = resp.json()

    resultats = []
    for item in data.get("vulnerabilities", []):
        cve = item["cve"]
        desc = next((d["value"] for d in cve.get("descriptions", [])
                     if d.get("lang") == "en"), "")
        metrics = cve.get("metrics", {})
        cvss = None
        if "cvssMetricV31" in metrics:
            cvss = metrics["cvssMetricV31"][0]["cvssData"]["baseScore"]
        resultats.append(ResultatCVE(
            cve_id=cve["id"],
            url=f"https://nvd.nist.gov/vuln/detail/{cve['id']}",
            description=desc[:300],
            cvss_score=cvss
        ))
    return resultats

Resources : données en lecture seule

Les Resources exposent des données que le LLM peut consulter, identifiées par des URIs. Elles ne peuvent pas modifier l'état du système — elles sont en lecture seule.

import json
from pathlib import Path

@mcp.resource("config://serveur/parametres")
def lire_configuration() -> str:
    # Configuration actuelle du serveur au format JSON.
    return json.dumps({
        "version": "1.0.0",
        "environnement": "production",
        "features_actives": ["recherche_cve", "export_pdf"],
        "limites": {"requetes_par_minute": 60}
    }, indent=2, ensure_ascii=False)

@mcp.resource("fichiers://{chemin}")
def lire_fichier(chemin: str) -> str:
    # Lit le contenu d'un fichier dans le répertoire data/.
    # URI template: fichiers://nom-du-fichier.txt
    base = Path("./data").resolve()
    cible = (base / chemin).resolve()
    # Protection contre le path traversal
    if not cible.is_relative_to(base):
        raise ValueError("Accès interdit : chemin hors du répertoire autorisé")
    if not cible.exists():
        raise FileNotFoundError(f"Fichier introuvable : {chemin}")
    return cible.read_text(encoding="utf-8")

Prompts : templates paramétrables

Les Prompts sont des templates de messages que les utilisateurs peuvent sélectionner depuis l'interface host pour standardiser des requêtes récurrentes.

@mcp.prompt()
def analyser_incident(ip_source: str, contexte: str = "") -> str:
    # Génère un prompt structuré pour l'analyse d'un incident de sécurité.
    return f"Tu es un analyste SOC niveau 2. Analyse l'activité de l'IP {ip_source}.  Contexte : {contexte or 'Aucun contexte fourni.'}  Etapes : 1) Recherche les alertes des 48h. 2) Identifie les patterns. 3) Conclus avec niveau de confiance."

Transport stdio : intégration Claude Desktop

Le transport stdio est le mode le plus courant : le host lance le serveur MCP comme sous-processus et communique via stdin/stdout. C'est le transport natif de Claude Desktop et Cursor.

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

Configuration dans Claude Desktop (fichier JSON de configuration) :

{
  "mcpServers": {
    "mon-serveur": {
      "command": "/usr/bin/python3",
      "args": ["/chemin/absolu/vers/server.py"],
      "env": {
        "API_KEY": "votre-cle-api"
      }
    }
  }
}

Chemin du fichier config : ~/Library/Application Support/Claude/claude_desktop_config.json (macOS), ~/.config/claude/claude_desktop_config.json (Linux). Redémarrez Claude Desktop après modification.

Transport SSE : déploiement sur serveur distant

Le transport SSE déploie le serveur MCP sur une machine distante via HTTP. Il est adapté aux accès à des ressources réseau internes ou au partage entre plusieurs utilisateurs.

mcp = FastMCP(
    "api-interne",
    host="0.0.0.0",
    port=8080,
)

if __name__ == "__main__":
    mcp.run(transport="sse")
# Configuration client pour SSE
{
  "mcpServers": {
    "api-interne": {
      "url": "http://192.168.1.100:8080/sse"
    }
  }
}
Comparatif transports MCP : stdio vs SSE
Critère Transport stdio Transport SSE
DéploiementLocal (même machine)Local ou distant via HTTP
SetupTrès simple (chemin + args)URL + port à exposer
Multi-utilisateursNon (1 process par session)Oui (connexions concurrentes)
AuthentificationImplicite (droits OS)OAuth 2.1, API keys, JWT
Docker/K8sComplexeNatif (port mapping)
Cas d'usageDev, distribution end-userProd centralisée, SaaS, équipes

Tester avec MCP Inspector

MCP Inspector est l'outil de débogage officiel. Il permet de tester votre serveur via une interface web sans LLM.

# Ouvre automatiquement http://localhost:5173
npx @modelcontextprotocol/inspector python server.py

# Pour un serveur SSE distant
npx @modelcontextprotocol/inspector --url http://192.168.1.100:8080/sse

L'Inspector affiche trois onglets : Tools (test avec paramètres libres), Resources (navigation), Prompts (prévisualisation). L'onglet Console affiche les requêtes JSON-RPC brutes — essentiel pour déboguer les schémas de paramètres.

Publication sur Smithery.ai

Smithery est le principal registre de serveurs MCP. La publication nécessite un smithery.yaml à la racine du projet.

# smithery.yaml
name: mon-serveur-securite-mcp
version: "1.0.0"
description: "Recherche CVE et analyse de sécurité via MCP"
license: "MIT"

startCommand:
  type: stdio
  configSchema:
    type: object
    properties:
      nvd_api_key:
        type: string
        description: "Clé API NVD (optionnelle)"
    required: []
  commandFunction: |
    (config) => ({
      command: "python",
      args: ["-m", "mon_serveur_mcp"],
      env: config.nvd_api_key ? { "NVD_API_KEY": config.nvd_api_key } : {}
    })
npm install -g @smithery/cli
smithery login
smithery publish

Avant de publier — checklist sécurité

Les serveurs MCP publiés sur des registres publics peuvent être vecteurs d'attaques si mal sécurisés — consultez notre guide sur les risques supply chain MCP. Checklist minimum : descriptions précises sur tous les tools, validation Pydantic des paramètres, timeout sur toutes les opérations réseau, aucun credential hardcodé, gestion d'erreur sans stack trace exposée.

Sécurité basique d'un serveur MCP

La sécurité des agents MCP est critique. Validez systématiquement les entrées :

from pydantic import BaseModel, Field
from typing import Annotated

class RequeteRecherche(BaseModel):
    termes: Annotated[str, Field(min_length=2, max_length=200)]
    limite: Annotated[int, Field(ge=1, le=100)] = 10

@mcp.tool()
def rechercher(requete: RequeteRecherche) -> list[dict]:
    # Pydantic valide automatiquement avant l'appel
    try:
        return effectuer_recherche(requete.termes, requete.limite)
    except TimeoutError:
        raise ValueError("Delai depasse (30s)")
    except Exception:
        raise RuntimeError("Erreur interne. Consultez les logs.")

Déploiement en production : pattern recommandé

Pour un serveur MCP SSE en production, le pattern recommandé combine un reverse proxy Nginx, une authentification OAuth 2.1 et un logging structuré :

# Dockerfile pour serveur MCP SSE
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install -e ".[cli]"
COPY . .
# Utilisateur non-root obligatoire
RUN useradd -r -u 1001 mcpuser
USER mcpuser
EXPOSE 8080
CMD ["python", "-m", "mon_serveur_mcp", "--transport", "sse", "--host", "0.0.0.0", "--port", "8080"]

Configuration Nginx avec rate limiting :

limit_req_zone $binary_remote_addr zone=mcp:10m rate=60r/m;

server {
    listen 443 ssl;
    server_name mcp.interne.example.com;

    location /sse {
        limit_req zone=mcp burst=10 nodelay;
        proxy_pass http://localhost:8080;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        chunked_transfer_encoding on;
    }
}

Questions fréquentes

Quelle différence entre MCP et une API REST classique ?

Une API REST expose des endpoints fixes que le client doit connaître à l'avance. MCP ajoute un mécanisme de découverte dynamique : le LLM découvre les capabilities au moment de la connexion, sans configuration préalable. MCP ajoute aussi un schéma JSON Schema pour chaque tool (validation automatique des paramètres) et une description sémantique permettant au LLM de comprendre quand utiliser quel tool. Concrètement, votre serveur MCP appellera probablement des APIs REST en interne — MCP est une couche d'abstraction par-dessus, pas un remplacement.

Transport stdio ou SSE : comment choisir ?

Choisissez stdio pour des serveurs locaux distribués aux utilisateurs finaux — plus simple, plus sécurisé (pas de port réseau), Claude Desktop et Cursor le supportent nativement. Choisissez SSE pour un déploiement centralisé (plusieurs utilisateurs partageant le même serveur), Docker/Kubernetes, une authentification OAuth centralisée, ou quand le serveur nécessite l'accès à des ressources réseau internes inaccessibles depuis les machines utilisateurs.

Comment déboguer un serveur MCP qui ne s'affiche pas dans Claude Desktop ?

1. Vérifiez que le chemin Python est absolu (/usr/bin/python3 pas python3). 2. Testez le serveur en mode stdio — il doit répondre à une requête initialize JSON-RPC. 3. Consultez les logs dans ~/Library/Logs/Claude/mcp-server-NOMDUVOTRESERVEUR.log (macOS) ou ~/.config/claude/logs/ (Linux). 4. Redémarrez Claude Desktop après chaque modification du fichier config — les changements ne sont pas rechargés à chaud.

MCP fonctionne-t-il avec des LLMs autres que Claude ?

Oui. MCP est un protocole ouvert — la spécification est sur spec.modelcontextprotocol.io. En 2026, les clients MCP compatibles incluent Claude Desktop, Cursor, Windsurf, Zed, des extensions VS Code, et des frameworks comme LangChain, LlamaIndex et CrewAI. N'importe quel LLM supportant le function calling (GPT-4o, Gemini, Mistral, modèles open source via Ollama) peut être connecté à un serveur MCP via un client compatible.

Quels sont les risques de sécurité d'un serveur MCP en production ?

Les risques principaux sont : le tool poisoning (injection d'instructions malveillantes dans les descriptions), l'escalade de privilèges si les tools accèdent à des ressources trop sensibles sans confirmation utilisateur, et le déni de service sans limites de taux. Pour les contre-mesures détaillées, consultez notre guide Sécuriser un déploiement MCP en production.

Pour aller plus loin : introduction au protocole MCP, guide Claude Desktop et MCP, sécurisation des agents MCP, risques supply chain des registres MCP.