取消
用戶端可以放棄一次呼叫:使用者按了停止,或是逾時時間到了。
這時 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(舊版連線)會關閉取消功能。
進度與取消是執行中的工具和它的呼叫端之間的事。它為你這個伺服器操作者寫下的記錄,走的是另一個管道:記錄。