Un serveur MCP expose des ressources et outils à un modèle via un client. Le protocole ne transforme pas un outil dangereux en action sûre. Il faut authentifier le client, autoriser chaque opération et traiter tous les arguments comme non fiables.
Cartographier la surface
Inventoriez outils, ressources, prompts, transports et systèmes appelés. Classez les effets : lecture, écriture, suppression, exécution ou accès à des données sensibles. N’exposez pas une fonction administrative générique lorsque trois fonctions étroites et autorisables suffisent.
Authentifier et autoriser
Utilisez le mécanisme prévu pour le transport et le contexte, puis associez l’identité à des scopes minimaux. Vérifiez l’autorisation dans chaque handler avec utilisateur, tenant et ressource. Ne laissez pas le modèle fournir un identifiant d’organisation arbitraire. Pour les actions sensibles, exigez approbation ou réauthentification.
Valider et isoler
Validez schémas, longueurs, chemins, URLs et types. Bloquez traversal de répertoires et sorties réseau non autorisées. Exécutez les commandes dans un environnement limité, sans secrets globaux. Séparez comptes de service et environnements ; rendez les écritures idempotentes.
Observer sans fuir
Tracez identité, outil, décision d’autorisation, durée et résultat, mais masquez tokens, arguments sensibles et contenus. Ajoutez quotas, timeouts, annulation et alertes sur répétitions ou volumes anormaux. Testez injection indirecte, confusion de tenant, SSRF, doublons et révocation d’accès.
FAQ
HTTPS suffit-il ?
Non. Il protège le transport, pas les permissions ni les outils.
Le client MCP peut-il confirmer une action ?
Certains clients le peuvent, mais le serveur doit appliquer sa propre politique.
Un serveur local est-il sûr par défaut ?
Non. Il peut accéder à des fichiers, variables ou commandes sensibles.
Garde-fous côté serveur
Validez les arguments avant toute action et appliquez une liste d’autorisation. Exemple FastMCP :
from pathlib import Path
from pydantic import BaseModel, Field
from fastmcp import FastMCP
mcp = FastMCP("documents")
ROOT = Path("/srv/documents").resolve()
class ReadArgs(BaseModel):
relative_path: str = Field(min_length=1, max_length=200, pattern=r"^[\w./-]+$")
@mcp.tool()
def read_document(args: ReadArgs) -> str:
target = (ROOT / args.relative_path).resolve()
if ROOT not in target.parents:
raise ValueError("Chemin interdit")
if target.suffix not in {".md", ".txt"}:
raise ValueError("Type de fichier interdit")
return target.read_text(encoding="utf-8")[:20_000]Exécutez le processus avec un utilisateur sans privilèges, un système de fichiers en lecture seule et des sorties réseau restreintes. Authentifiez chaque client, rattachez l’identité aux traces et exigez une confirmation humaine pour les outils destructifs. Les erreurs retournées au modèle ne doivent contenir ni secret, ni chemin interne, ni trace complète. Ajoutez enfin des limites de débit et un quota par outil.