Cancelamento
Tradução automática
Esta página foi traduzida automaticamente a partir da documentação em inglês, e a página em inglês é a versão de referência. Se algo parecer errado, Traduções explica como avisar.
Um cliente pode desistir de uma chamada: o usuário apertou o botão de parar, ou um timeout se esgotou.
Quando isso acontece, o SDK cancela o seu handler. O await em que ele está esperando lança uma exceção, a função é desempilhada, e nada do que ela retornar é enviado. A maioria dos handlers não precisa fazer nada a respeito.
Dois tipos precisam: um handler com algo para limpar, e um handler que é um def comum.
Faça a limpeza em uma ferramenta async def
Coloque a limpeza em um finally:
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)
- O
finallyexecuta não importa como a ferramenta termine: ela retornou, lançou uma exceção ou foi cancelada. - Uma limpeza que precisa fazer
awaitexigeshield=True. Em um handler cancelado, todoawaitseguinte também lança uma exceção, então sem a proteçãorelease_holdpararia na primeira linha. - Nada consegue cancelar um bloco protegido, então dê a ele um limite de tempo. Aqui são
5segundos.
Tip
Use finally, não except. O cancelamento precisa continuar subindo depois que a sua limpeza
termina, e um finally permite isso.
Pare antes do fim em uma ferramenta def comum
Uma ferramenta def comum roda em uma thread, e nada consegue interromper uma thread de fora. A ferramenta precisa perguntar:
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()não faz nada enquanto a chamada está ativa, e lança uma exceção assim que ela é cancelada. Chame essa função entre unidades de trabalho.- Aqui a limpeza também vai em um
finally. Nada em uma thread faz await, então a limpeza não precisa de proteção. - Uma ferramenta
defque nunca pergunta roda até o fim, e o resultado dela é descartado.
Onde se aplica
As funções de prompt e de recurso são canceladas exatamente como as ferramentas.
Funciona do mesmo jeito sobre stdio e Streamable HTTP. Com o Client deste SDK, desistir significa cancelar a tarefa que aguarda call_tool, ou deixar o read_timeout_seconds da chamada se esgotar.
Warning
Duas opções do Streamable HTTP impedem que a notícia chegue ao seu handler: json_response=True em uma
conexão 2026-07-28, e stateless_http=True em uma conexão legada. Nesses casos, o handler roda até
o fim, não importa o que o cliente tenha feito.
Resumo
- Quando o cliente desiste de uma chamada, o SDK cancela o handler: ferramenta, prompt ou recurso.
async def: faça a limpeza em umfinally, e coloque a limpeza que faz await dentro deanyio.move_on_after(seconds, shield=True).defcomum: chameanyio.from_thread.check_cancelled()entre unidades de trabalho, ou a ferramenta roda até o fim. Umfinallysimples faz a limpeza.json_response=True(conexões modernas) estateless_http=True(conexões legadas) desligam o cancelamento.
Progresso e cancelamento ficam entre uma ferramenta em execução e quem a chamou. As linhas que ela registra em log para você, a pessoa que opera o servidor, são um canal diferente: Logging.