Перейти до змісту

Скасування

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

Клієнт може відмовитися від виклику: користувач натиснув кнопку зупинки або сплив тайм-аут.

Тоді SDK скасовує ваш обробник. Вираз await, на якому він чекає, викидає виняток, функція згортається, і ніщо з того, що вона повертає, не надсилається. Більшості обробників нічого з цим робити не треба.

А от двом видам треба: обробнику, якому є що прибрати за собою, і обробнику, оголошеному як звичайний def.

Очищення в інструменті з async def

Помістіть очищення в блок 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)
  • Блок finally виконується, хоч би як завершився інструмент: повернув результат, викинув виняток чи був скасований.
  • Очищенню, якому потрібен await, необхідний shield=True. У скасованому обробнику кожен наступний await теж викидає виняток, тож без цього захисту функція release_hold зупинилася б на першому ж рядку.
  • Захищений блок ніщо не може скасувати, тому обмежте його в часі. Тут це 5 секунд.

Tip

Використовуйте finally, а не except. Після очищення скасування має поширюватися далі вгору, і finally це дозволяє.

Дострокова зупинка в інструменті зі звичайним def

Інструмент зі звичайним def виконується в потоці, а перервати потік ззовні неможливо. Інструмент має запитати сам:

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() нічого не робить, поки виклик активний, і викидає виняток, щойно його скасовано. Викликайте її між порціями роботи.
  • Очищення і тут розміщують у 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 (з'єднання старого покоління) вимикають скасування.

Перебіг виконання і скасування стосуються інструмента, що виконується, і того, хто його викликав. Рядки, які він записує в лог для вас, людини, яка керує сервером, — це інший канал: Логування.