Tutoriel : les racines en pratique
Ce tutoriel montre comment implémenter les roots dans une application de conversion vidéo. Avant d’entrer dans les étapes, rappelez-vous le point établi dans la leçon théorique, car il commande tout le code qui suit : le SDK ne fait rien appliquer. Déclarer des roots ne protège aucun fichier — c’est un canal d’information entre le client et le serveur, et la vérification d’accès reste entièrement à votre charge, à l’étape 6. Un serveur qui oublie cette vérification fonctionne parfaitement… en accédant à tout.
Étape 1 : Définir les roots
Idéalement, c’est l’utilisateur qui dicte quels fichiers et dossiers le serveur MCP peut atteindre — et ce choix de conception n’est pas anodin. Des chemins codés en dur dans le serveur obligeraient chaque utilisateur à adapter le code à sa machine ; des chemins passés au lancement font de la permission un acte explicite, visible dans la ligne de commande. C’est le même modèle mental que le montage de volumes : on donne accès à ce qu’on nomme, rien d’autre.
Dans cet exemple, le programme accepte des arguments CLI qui sont interprétés comme les chemins auxquels l’utilisateur veut accorder l’accès :
# L'utilisateur spécifie les dossiers accessibles au démarrage
uv run main.py /Users/alice/Videos /Users/alice/Desktop
Ces chemins sont passés au MCPClient lors de l’initialisation.
Étape 2 : Créer des objets Root
Convertissez les chemins en objets Root du SDK MCP :
from mcp.types import Root
from pathlib import Path
def create_roots(paths: list[str]) -> list[Root]:
roots = []
for path in paths:
abs_path = Path(path).resolve()
roots.append(Root(
uri=f"file://{abs_path}",
name=abs_path.name
))
return roots
Étape 3 : Callback de roots
Le serveur peut demander la liste des roots via un callback. Définissez ce callback côté client :
async def roots_callback() -> list[Root]:
# Retourne la liste des roots que l'utilisateur a approuvées
return client_roots # défini au démarrage de l'application
Passez-le lors de la création de la session :
async with ClientSession(
read,
write,
roots_callback=roots_callback # ← ici
) as session:
await session.initialize()
Étape 4 : Utiliser les roots dans les outils
Côté serveur, vos outils peuvent récupérer les roots et les utiliser :
@mcp.tool()
async def list_accessible_files(
extension: str = Field(description="Extension de fichier (ex: mp4)"),
ctx: Context
) -> list[str]:
# Récupérer les roots disponibles
roots_result = await ctx.session.list_roots()
files = []
for root in roots_result.roots:
root_path = Path(root.uri.replace("file://", ""))
# Trouver tous les fichiers avec l'extension demandée
for f in root_path.rglob(f"*.{extension}"):
files.append(str(f))
return files
Étape 5 : Accéder aux roots
Pour accéder aux roots dans un outil, utilisez ctx.session.list_roots() :
@mcp.tool()
async def convert_video(
filename: str = Field(description="Nom du fichier vidéo à convertir"),
ctx: Context
) -> str:
roots_result = await ctx.session.list_roots()
# Chercher le fichier dans les roots
video_path = find_file_in_roots(filename, roots_result.roots)
if not video_path:
return f"Fichier '{filename}' introuvable dans les dossiers accessibles"
# Convertir...
return await do_convert(video_path)
Étape 6 : Autoriser l’accès
Voici l’étape qui fait la différence entre une application sûre et une application qui semble l’être. La vérification doit travailler sur des chemins résolus — c’est ce que fait le code ci-dessous — car un chemin relatif contenant .. peut désigner n’importe quel point du disque tout en paraissant anodin. Comparer des chaînes de caractères ne suffit pas ; on compare des chemins canoniques.
def find_file_in_roots(filename: str, roots) -> Path | None:
for root in roots:
root_path = Path(root.uri.replace("file://", ""))
# Chercher récursivement
matches = list(root_path.rglob(filename))
if matches:
return matches[0]
return None
def is_path_in_roots(path: Path, roots) -> bool:
for root in roots:
root_path = Path(root.uri.replace("file://", "")).resolve()
if path.resolve().is_relative_to(root_path):
return True
return False
Flux complet
Utilisateur: "Convertis biking.mp4 en MOV"
↓
Claude appelle list_accessible_files(extension="mp4")
↓
Serveur → list_roots() → roots_callback → ["/Users/alice/Videos", ...]
↓
Serveur parcourt les roots → trouve "/Users/alice/Videos/biking.mp4"
↓
Claude appelle convert_video(filename="biking.mp4")
↓
Serveur trouve le fichier dans les roots → convertit
↓
"biking.mov créé dans /Users/alice/Videos/"
Points clés
- Les roots sont définies par l’utilisateur lors du démarrage de l’application
- Le SDK ne les applique pas automatiquement — vous devez vérifier vous-même
- Toujours vérifier
is_path_in_roots()avant tout accès au système de fichiers - Les roots rendent l’expérience fluide : les utilisateurs n’ont pas à taper des chemins complets