Aller au contenu

Annulation

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 client peut abandonner un appel : l’utilisateur a appuyé sur stop, ou un délai d’attente a expiré.

Dans ce cas, le SDK annule votre gestionnaire (handler). Le await sur lequel il est en attente lève une exception, la fonction est dépilée, et rien de ce qu’elle renvoie n’est envoyé. La plupart des gestionnaires n’ont rien à faire de particulier.

Deux catégories font exception : un gestionnaire qui a quelque chose à nettoyer, et un gestionnaire écrit comme un simple def.

Nettoyer dans un outil async def

Placez le nettoyage dans un finally :

server.py
import anyio

from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

holds: set[str] = set()


async def take_payment(title: str) -> None:
    await anyio.sleep(30)  # the customer is typing a card number


async def release_hold(title: str) -> None:
    await anyio.sleep(0.1)  # a round trip to the stock system
    holds.discard(title)


@mcp.tool()
async def order_book(title: str) -> str:
    """Hold a copy of a book while the customer pays for it."""
    holds.add(title)
    try:
        await take_payment(title)
        return f"Ordered {title!r}."
    finally:
        with anyio.move_on_after(5, shield=True):
            await release_hold(title)
  • Le finally s’exécute quelle que soit la façon dont l’outil se termine : il a renvoyé une valeur, il a levé une exception ou il a été annulé.
  • Un nettoyage qui doit faire un await a besoin de shield=True. Dans un gestionnaire annulé, chaque await suivant lève lui aussi une exception : sans cette protection, release_hold s’arrêterait dès sa première ligne.
  • Rien ne peut annuler un bloc protégé, alors donnez-lui une limite de temps. Ici, elle est de 5 secondes.

Tip

Utilisez finally, pas except. L’annulation doit continuer à remonter une fois votre nettoyage terminé, et un finally la laisse passer.

S’arrêter tôt dans un outil def simple

Un outil def simple s’exécute dans un thread, et rien ne peut interrompre un thread de l’extérieur. C’est à l’outil de poser la question :

server.py
import time

import anyio.from_thread

from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

offline: set[str] = set()


def index_book(title: str) -> None:
    time.sleep(1)  # slow work with nothing to await


@mcp.tool()
def rebuild_index(titles: list[str]) -> str:
    """Take search offline and rebuild its index, one book at a time."""
    offline.add("search")
    try:
        for title in titles:
            anyio.from_thread.check_cancelled()
            index_book(title)
        return f"Indexed {len(titles)} books."
    finally:
        offline.discard("search")
  • anyio.from_thread.check_cancelled() ne fait rien tant que l’appel est en cours, et lève une exception une fois qu’il a été annulé. Appelez-la entre deux unités de travail.
  • Ici aussi, le nettoyage va dans un finally. Dans un thread, il n’y a aucune attente asynchrone, donc aucune protection n’est nécessaire.
  • Un outil def qui ne pose jamais la question s’exécute jusqu’au bout, et son résultat est jeté.

Où cela s’applique

Les fonctions de prompt et de ressource sont annulées exactement comme les outils.

Cela fonctionne de la même façon avec stdio et Streamable HTTP. Avec la classe Client de ce SDK, abandonner revient à annuler la tâche qui attend call_tool, ou à laisser son délai read_timeout_seconds expirer.

Warning

Deux options de Streamable HTTP empêchent la nouvelle d’atteindre votre gestionnaire : json_response=True sur une connexion 2026-07-28, et stateless_http=True sur une connexion historique. Dans ces cas, le gestionnaire s’exécute jusqu’au bout, quoi qu’ait fait le client.

Récapitulatif

  • Quand le client abandonne un appel, le SDK annule le gestionnaire : outil, prompt ou ressource.
  • async def : nettoyez dans un finally, et placez tout nettoyage qui comporte une attente asynchrone dans anyio.move_on_after(seconds, shield=True).
  • def simple : appelez anyio.from_thread.check_cancelled() entre deux unités de travail, sinon l’outil s’exécute jusqu’au bout. Un simple finally se charge du nettoyage.
  • json_response=True (connexions modernes) et stateless_http=True (connexions historiques) désactivent l’annulation.

La progression et l’annulation se jouent entre un outil en cours d’exécution et son appelant. Les lignes qu’il journalise pour vous, la personne qui exploite le serveur, passent par un autre canal : Journalisation.