Qu’est-ce que le Model Context Protocol (MCP) et pourquoi c’est utile en Python
Le Model Context Protocol (MCP) est un protocole standardisé qui permet à une application d’exposer des outils, des ressources et des prompts à un modèle (via un client compatible), de manière structurée et contrôlée. L’objectif est de sortir d’une intégration “ad hoc” où chaque LLM consomme des API propriétaires, pour passer à une couche d’outillage uniforme.
Concrètement, MCP sert à centraliser l’accès à des capacités (lecture de fichiers, requêtes SQL, accès à un CRM, recherche interne) tout en réduisant les risques via des permissions et validations strictes. En Python, c’est particulièrement puissant grâce à la richesse de l’écosystème data et API.
Checklist de prérequis avant d’implémenter MCP en Python
Avant d’écrire du code, clarifiez le rôle de votre composant : serveur MCP (exposer des outils) ou client MCP (consommer des outils). Côté technique, prévoyez :
- Python 3.10+ (idéalement 3.11/3.12)
- Un environnement isolé (venv/uv)
- Une politique de secrets via variables d’environnement
- Le choix du transport : STDIO (le plus simple en local) ou HTTP
Guide étape par étape : comment implémenter MCP en Python
Étape 1 : cadrer le contexte et les permissions
Un outil MCP n’est pas une simple fonction utilitaire : c’est une capacité potentiellement dangereuse. Définissez les scopes (lecture seule vs écriture) et les allowlists (chemins autorisés, domaines d'API).
Étape 2 : définir les “tools” et leurs schémas d’entrée
Un bon outil MCP a un nom stable et un schéma d’arguments explicite. Utilisez des paramètres typés et des descriptions opérationnelles pour aider le modèle à comprendre quand et comment appeler l'outil.
Étape 3 : implémenter le serveur MCP
En Python, vous allez instancier un serveur, enregistrer des tools, puis lancer une boucle d’écoute. STDIO est souvent le transport privilégié pour les intégrations desktop locales.
Étape 4 : connecter un client compatible et tester en “boîte noire”
Testez les appels avec des paramètres valides ET invalides. La majorité des problèmes MCP viennent de schémas incomplets ou de retours non sérialisables en JSON.
Étape 5 : ajouter observabilité et robustesse
Instrumentez avec des logs structurés et des métriques de latence. Préférez l’idempotence pour les actions d'écriture afin d'éviter les duplications en cas de retry.
Exemple concret en Python : un mini serveur MCP avec deux tools
Voici un serveur minimal qui expose une recherche documentaire et une lecture de fichier sécurisée.
import os
import json
from pathlib import Path
from typing import Any, Dict, List
from mcp.server import Server
from mcp.types import Tool, ToolResult
BASE_DIR = Path(os.environ.get("MCP_BASE_DIR", "./allowed")).resolve()
DOCUMENTS = [
{"id": "doc_001", "title": "Guide Python", "body": "Python pour APIs, data et automatisation."},
{"id": "doc_002", "title": "MCP en pratique", "body": "Exposez des tools avec schémas et permissions."},
{"id": "doc_003", "title": "Sécurité", "body": "Validation stricte, allowlist, secrets et audit."},
]
def _safe_path(user_path: str) -> Path:
candidate = (BASE_DIR / user_path).resolve()
if not str(candidate).startswith(str(BASE_DIR)):
raise ValueError("Chemin refusé.")
return candidate
server = Server(name="demo-mcp-python")
@server.tool(
name="search_docs",
description="Recherche dans les documents locaux.",
input_schema={
"type": "object",
"properties": {
"query": {"type": "string", "minLength": 1},
"limit": {"type": "integer", "default": 5}
},
"required": ["query"]
}
)
def search_docs(query: str, limit: int = 5) -> ToolResult:
q = query.lower()
hits = [d for d in DOCUMENTS if q in (d["title"] + d["body"]).lower()]
return ToolResult(content=json.dumps({"hits": hits[:limit]}))
@server.tool(
name="read_file",
description="Lit un fichier texte dans BASE_DIR.",
input_schema={
"type": "object",
"properties": {
"path": {"type": "string"},
"max_bytes": {"type": "integer", "default": 50000}
},
"required": ["path"]
}
)
def read_file(path: str, max_bytes: int = 50000) -> ToolResult:
p = _safe_path(path)
if not p.exists() or not p.is_file():
raise FileNotFoundError("Fichier introuvable.")
text = p.read_bytes()[:max_bytes].decode("utf-8", errors="replace")
return ToolResult(content=json.dumps({"path": str(p), "content": text}))
if __name__ == "__main__":
server.run_stdio()
Erreurs fréquentes et pièges à éviter
Le piège le plus critique est de transformer MCP en “exécution de code arbitraire”. N'exposez jamais de shell brut. Un autre point de friction est la sérialisation : renvoyez toujours des types JSON simples (pas de datetime ou d'objets complexes).
Déboguer une implémentation MCP Python
En STDIO, le problème majeur est souvent un process qui écrit des logs sur stdout au lieu de stderr, polluant ainsi le protocole. Redirigez systématiquement vos logs techniques vers stderr.
Table de contrôle rapide : prérequis et garde-fous
| Point | Ce que vous devez verrouiller | Symptôme si mal fait |
|---|---|---|
| Transport | Logs sur stderr uniquement | Erreurs de parsing client |
| Schémas | Types stricts et bornes (maxLength) | Appels incohérents de l'IA |
| Sécurité | Allowlists et chemins sécurisés | Fuites de données / incidents |
| Sérialisation | Réponses JSON 100% compatibles | Erreurs client mystérieuses |