Progression
Traduction automatique
Cette page a été traduite automatiquement à partir de la documentation en anglais, et la page en anglais fait foi. Si quelque chose vous semble incorrect, la page Traductions explique comment le signaler.
Un outil qui met trente secondes et ne dit rien pendant trente secondes a l’air cassé.
Les notifications de progression règlent cela. L’outil indique où il en est ; le client décide quoi en afficher : une barre, une roue qui tourne, une ligne de journal.
La signaler depuis l’outil
Prenez un paramètre Context et appelez report_progress :
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bookshop")
@mcp.tool()
async def import_catalog(urls: list[str], ctx: Context) -> str:
"""Import book records from a list of catalog URLs."""
for done, url in enumerate(urls, start=1):
await ctx.report_progress(done, total=len(urls), message=f"Imported {url}")
return f"Imported {len(urls)} records."
Trois arguments, et c’est vous qui décidez de leur sens :
progress: où vous en êtes. La spécification exige qu’il augmente à chaque signalement ; ne répétez jamais une valeur et ne revenez jamais en arrière.total: la quantité totale, si vous la connaissez. Optionnel.message: une ligne lisible par un humain à propos de cette étape. Optionnel.
ctx est injecté grâce à son annotation de type et le modèle ne le voit jamais : le schéma d’entrée de import_catalog a une seule propriété, urls. La page L’objet Context est entièrement consacrée à cet objet ; la progression est l’une des choses qu’il vous apporte.
L’écouter depuis le client
Le client active la fonctionnalité appel par appel, en passant progress_callback= à call_tool :
import anyio
from mcp import Client
from server import mcp
async def show(progress: float, total: float | None, message: str | None) -> None:
print(f"{message} ({progress}/{total})")
async def main() -> None:
async with Client(mcp) as client:
result = await client.call_tool(
"import_catalog",
{"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
progress_callback=show,
)
print(result.structured_content)
anyio.run(main)
La fonction de rappel (callback) est une fonction async qui prend exactement ce que le serveur a signalé : progress, total, message.
Info
Client(mcp) se connecte directement à l’objet serveur, en mémoire : c’est le même client que celui sur lequel repose la page Tests. progress_callback est le même paramètre quel que soit le transport qu’utilise le Client ; le timing que vous allez observer est celui de la connexion en mémoire. Elle exécute votre fonction de rappel de façon synchrone, si bien que chaque signalement arrive avant que call_tool ne renvoie. Sur un vrai transport, les notifications font la course avec le résultat, et une fonction de rappel lente peut encore être en cours d’exécution après le retour de call_tool.
Essayer
Placez client.py à côté de server.py et lancez-le :
python client.py
Imported https://example.com/a.json (1/2)
Imported https://example.com/b.json (2/2)
{'result': 'Imported 2 records.'}
Chaque await ctx.report_progress(...) côté serveur est devenu un appel à show côté client, dans l’ordre, et les deux lignes se sont affichées avant que call_tool ne renvoie. La progression n’est pas empaquetée dans le résultat ; elle est diffusée pendant que l’outil travaille encore.
Warning
progress_callback appartient à l’appel, pas au Client. Il n’existe aucun argument de constructeur pour cela, parce que des appels différents veulent des fonctions de rappel différentes : l’un pilote une barre de téléchargement, le suivant une ligne de journal.
Check
Maintenant, supprimez progress_callback=show et relancez :
{'result': 'Imported 2 records.'}
Aucune erreur, aucun avertissement, même résultat. report_progress ne fait rien quand l’appelant n’a pas demandé la progression : vous signalez donc sans condition et n’avez jamais à vous demander si quelqu’un écoute.
Quand vous ne connaissez pas le total
total sert quand vous connaissez le dénominateur. Souvent, ce n’est pas le cas : vous videz un flux, parcourez un curseur, téléchargez quelque chose sans en-tête de longueur.
Omettez-le :
from collections.abc import AsyncIterator
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bookshop")
async def fetch_records(feed_url: str) -> AsyncIterator[str]:
for title in ("Dune", "Neuromancer", "Hyperion"):
yield f"{feed_url}#{title}"
@mcp.tool()
async def import_feed(feed_url: str, ctx: Context) -> str:
"""Import every record a catalog feed yields."""
imported = 0
async for record in fetch_records(feed_url):
imported += 1
await ctx.report_progress(imported, message=f"Imported {record}")
return f"Imported {imported} records."
La fonction de rappel reçoit total=None. Un client peut toujours montrer une activité (« 3 importés jusqu’ici… ») mais il ne peut pas afficher de pourcentage. N’inventez pas un total pour obtenir une plus jolie barre.
Tip
progress n’a pas à compter quelque chose de précis. Octets, lignes, pages : choisissez l’unité que l’utilisateur reconnaîtrait, et ne promettez qu’un total que vous pouvez tenir.
Récapitulatif
await ctx.report_progress(progress, total=None, message=None)depuis n’importe quel outil qui prend unContext.- Le client passe
progress_callback=àcall_tool: appel par appel, jamais sur leClient. - La fonction de rappel est
async (progress, total, message) -> Noneet se déclenche pendant que l’outil s’exécute encore. - Sans fonction de rappel sur l’appel,
report_progressne fait rien. Signalez sans condition. - Omettez
totalquand vous ne le connaissez pas ; la fonction de rappel reçoitNone.
La progression est ce qu’un outil en cours d’exécution montre à l’utilisateur. Les lignes qu’il journalise pour vous, la personne qui exploite le serveur, passent par un autre canal : la journalisation.