Скасування
Машинний переклад
Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.
Клієнт може відмовитися від виклику: користувач натиснув кнопку зупинки або сплив тайм-аут.
Тоді SDK скасовує ваш обробник. Вираз await, на якому він чекає, викидає виняток, функція згортається, і ніщо з того, що вона повертає, не надсилається. Більшості обробників нічого з цим робити не треба.
А от двом видам треба: обробнику, якому є що прибрати за собою, і обробнику, оголошеному як звичайний def.
Очищення в інструменті з async def
Помістіть очищення в блок 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)
- Блок
finallyвиконується, хоч би як завершився інструмент: повернув результат, викинув виняток чи був скасований. - Очищенню, якому потрібен
await, необхіднийshield=True. У скасованому обробнику кожен наступнийawaitтеж викидає виняток, тож без цього захисту функціяrelease_holdзупинилася б на першому ж рядку. - Захищений блок ніщо не може скасувати, тому обмежте його в часі. Тут це
5секунд.
Tip
Використовуйте finally, а не except. Після очищення скасування має поширюватися далі вгору,
і finally це дозволяє.
Дострокова зупинка в інструменті зі звичайним def
Інструмент зі звичайним def виконується в потоці, а перервати потік ззовні неможливо. Інструмент має запитати сам:
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()нічого не робить, поки виклик активний, і викидає виняток, щойно його скасовано. Викликайте її між порціями роботи. - Очищення і тут розміщують у
finally. У потоці немає асинхронних очікувань, тож захист не потрібен. - Інструмент
def, який жодного разу не запитує, виконується до кінця, а його результат відкидається.
Де це діє
Функції промптів і ресурсів скасовуються так само, як інструменти.
Через stdio і Streamable HTTP це працює однаково. Для класу Client із цього SDK відмовитися від виклику означає скасувати завдання, яке очікує на call_tool, або дати спливти його read_timeout_seconds.
Warning
З двома параметрами Streamable HTTP обробник про скасування не дізнається: json_response=True
на з'єднанні 2026-07-28 і stateless_http=True на з'єднанні старого покоління. Там обробник
виконується до кінця, хоч би що зробив клієнт.
Підсумки
- Коли клієнт відмовляється від виклику, SDK скасовує обробник: інструмент, промпт чи ресурс.
async def: виконуйте очищення уfinally, а очищення, якому потрібне асинхронне очікування, помістіть усерединуanyio.move_on_after(seconds, shield=True).- Звичайний
def: викликайтеanyio.from_thread.check_cancelled()між порціями роботи, інакше інструмент виконається до кінця. Для очищення достатньо звичайногоfinally. json_response=True(сучасні з'єднання) іstateless_http=True(з'єднання старого покоління) вимикають скасування.
Перебіг виконання і скасування стосуються інструмента, що виконується, і того, хто його викликав. Рядки, які він записує в лог для вас, людини, яка керує сервером, — це інший канал: Логування.