Cancellation、Progress、Pagination 与错误
学习目标
- 实现取消、进度、分页和错误处理的正确协议边界。
- 区分 stdio 与 Streamable HTTP 的取消信号。
- 知道 progressToken 是 Client 选择的关联 token,不是强制进度回调。
- 处理 opaque cursor、resultType、标准 JSON-RPC 错误和 Tool 执行错误。
前置知识
需要阅读 JSON-RPC 请求、响应、通知与 _meta 和 stdio 与 Streamable HTTP。
核心知识点
Cancellation:停止等待和停止工作
Client 可以取消仍在处理的请求:
- stdio:发送 notifications/cancelled,params.requestId 指向原 Request;
- Streamable HTTP:关闭该请求的响应流,Server 把断开视为取消;
- Server 收到取消后应尽快停止工作、释放资源,并且现代规范要求不要再为已取消请求发送结果;
- 取消存在竞态,通知可能晚于完成;Client 应忽略迟到的响应。
notifications/cancelled 不能被 Server 用来随意取消其它请求。Server 发送它的唯一现代用途是终止 subscriptions/listen(尤其 stdio 的共享通道)。
Progress:可选的工作播报
Client 若希望收到进度,在请求 params._meta 放一个唯一的 progressToken,值为字符串或整数。Server 可以发 notifications/progress:
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "job-42",
"progress": 50,
"total": 100,
"message": "reading files"
}
}progress 必须随通知增加,total 和 message 可选。Server 可以不发进度;Progress 不替代最大超时,不能让 Client 无限等待。
Pagination:不透明 cursor
支持分页的现代方法:
- resources/list;
- resources/templates/list;
- prompts/list;
- tools/list。
Client 首次请求不带 cursor,结果若有 nextCursor,就把这个值原样放进下一次请求。cursor 是不透明字符串,不能解析为页码;空字符串也是合法 cursor,不应当等同于“没有更多”。
Server 决定页面大小,Client 应以 nextCursor 是否存在判断结束。cursor 失效通常是 -32602 Invalid params;Client 可丢弃已有页面并从头获取。
错误分层
JSON-RPC 标准错误:
- -32700 Parse error;
- -32600 Invalid Request;
- -32601 Method not found;
- -32602 Invalid params;
- -32603 Internal error。
2026-07-28 规范保留的 MCP 错误:
- -32020 HeaderMismatch;
- -32021 MissingRequiredClientCapability;
- -32022 UnsupportedProtocolVersion。
资源不存在使用 -32602,旧版 -32002 仅为兼容接收保留。Tool 的业务执行失败通常是 result.isError=true,让模型能读取并调整;协议结构错误或未知方法则是 JSON-RPC error。
取消、进度、分页和错误的状态图
用标准库消费分页并打印进度
用途:代码展示“不解析 cursor、按 nextCursor 循环、进度不是完成信号”的最小 Client 逻辑。
pages = {
None: {"items": ["a", "b"], "nextCursor": "opaque-1"},
"opaque-1": {"items": ["c"], "nextCursor": None},
}
def list_all():
cursor = None
items = []
while True:
page = pages[cursor]
items.extend(page["items"])
print(f"progress={len(items)}")
next_cursor = page.get("nextCursor")
if next_cursor is None:
return items
cursor = next_cursor
print("items:", list_all())输出:
progress=2
progress=3
items: ['a', 'b', 'c']注意:真实 JSON-RPC 请求的每一页都要附带新的 _meta,且下一页 id 应与上一页不同。上述 pages 字典只是模拟 Server。
幂等和重试
超时或连接断开后,Client 不知道 Server 是否已产生副作用。读取和纯查询通常可以重试;创建订单、付款、删除等动作要依赖幂等键、显式业务句柄或用户确认。取消不是回滚,Server 可能已经完成操作,必须在业务语义中明确这一点。
术语与白话
Cancellation
术语:请求仍在处理时,发送方表达不再需要结果。 白话:停止等餐并请厨房尽快停火,不保证已经做好的菜会自动撤销。
ProgressToken
术语:Client 选出的进度关联标识。 白话:订单号,进度广播用它告诉你是哪一单。
Cursor
术语:分页位置的不透明 token。 白话:书签;只负责下一页,不要拆开研究页码。
Tool Execution Error
术语:工具已进入结果层但执行失败,通过 isError 表示。 白话:订单格式没问题,业务操作失败,模型有机会改参数或解释给用户。
易混点
取消不是错误响应
现代 Server 对已取消请求不应再发结果或 error;发送方停止等待。不要为了“完整日志”强行制造一个错误响应,让 Client 误以为服务端仍然处理了请求。
Progress 不是心跳,也不是保证
Server 可以不发 progress;收到 progress 也不代表最终结果一定会到。即使不断进度,Client 仍应执行最大超时。
nextCursor 不是数组下标
opaque cursor 的内部编码由 Server 决定,可能包含签名、快照或数据库游标。Client 修改它会导致 -32602 或更糟的跨租户数据错误。
error 与 isError 不是同一回事
无效方法、坏参数和 HeaderMismatch 属于协议错误;可修正的业务失败应作为 Tool 结果。错误分类错误会让模型无法自愈,或把协议问题错误地暴露成普通文本。
-32002 不是现代资源不存在码
2026-07-28 用 -32602;Client 可以兼容读取旧 Server 的 -32002,但新 Server 不应继续发旧码。
课后小问(含解析)
问题 1:收到一条 progress 后,Client 是否可以取消超时计时?
解析:可以把 progress 作为“工作仍在进行”的信号,但规范仍建议设置绝对最大超时。否则恶意或失常 Server 可以永远发送进度而不返回结果。
问题 2:HTTP Client 要不要发送 notifications/cancelled?
解析:现代 Streamable HTTP 的取消信号是关闭对应响应流,不要求也不期待发送该通知;stdio 没有每请求流,才必须发送 notifications/cancelled。
小结
Cancellation 表示停止等待并协作释放资源,Progress 是可选播报,Pagination 使用不透明 cursor,Error 要按协议错误与 Tool 执行错误分层。现代请求取消后通常没有错误响应;重试是否安全取决于业务幂等性而不是 MCP 自动保证。
快速回顾
- stdio 取消发送通知;HTTP 取消关闭响应流。
- progressToken 在每请求 _meta 中,由 Client 选择且必须在活动请求中唯一。
- list 方法用 nextCursor 分页,cursor 原样回传。
- -32020/-32021/-32022 是现代 MCP 保留错误。
- 资源不存在用 -32602;Tool 可修正失败用 isError=true。
阿源的知识库