跳转至

取消

机器翻译

本页由英文文档自动翻译而来,以英文页面为准。如果有读起来不对的地方,翻译页面说明了如何反馈。

客户端可以放弃一次调用:用户按了停止,或者超时时间到了。

这时,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。线程里没有任何 await,所以不需要屏蔽。
  • 从不询问的 def 工具会一直运行到结束,结果则被丢弃。

适用范围

提示词函数和资源函数的取消方式与工具完全相同。

在 stdio 和 Streamable HTTP 上行为一致。使用这个 SDK 的 Client 时,放弃调用就是取消正在等待 call_tool 的任务,或者让它的 read_timeout_seconds 耗尽。

Warning

有两个 Streamable HTTP 选项会让处理函数收不到取消的消息:2026-07-28 连接上的 json_response=True,以及旧版连接上的 stateless_http=True。这两种情况下,无论客户端做了什么,处理函数都会运行到结束。

回顾

  • 客户端放弃一次调用时,SDK 会取消处理函数:工具、提示词或资源都一样。
  • async def:在 finally 中清理,需要 await 的清理代码放进 anyio.move_on_after(seconds, shield=True)。
  • 普通 def:在工作单元之间调用 anyio.from_thread.check_cancelled(),否则工具会运行到结束。清理用普通的 finally 就行。
  • json_response=True(新版连接)和 stateless_http=True(旧版连接)会关闭取消。

进度和取消发生在运行中的工具和它的调用方之间。它为你(运维这台服务器的人)记录的日志走的是另一条通道:日志。