这篇适合谁
适合已经能跑通基础工具和资源的 MCP 服务器开发者,现在需要处理耗时任务、支持取消,或者统一错误返回的人。读完你应该知道每个特性解决什么问题,以及旧资料里哪些部分已经属于旧版。
基础之外的六个特性
除了基础工具和资源,还有六个特性:进度通知、请求取消、资源模板、应用生命周期、旧版日志控制和错误处理模式[1]。每个特性解决的问题不同,按需挑用即可。
进度通知:长任务怎么告诉用户进展
数据处理、文件下载、调用外部 API 这类耗时操作,需要进度通知让用户知道在做什么[1]。服务器在处理 tools/call 请求的过程中发送进度更新,最后再返回结果[1]。下面的 Python 服务器在每处理完一项后发送一条 ProgressNotification:
await ctx.send_notification(
ProgressNotification(
progressToken=ctx.request_id,
progress=i + 1,
total=total,
message=f"Processed {i + 1}/{total}: {item}"
)
)客户端则要在调用工具之前注册处理函数[1]:
async def handle_progress(notification):
"""Handle progress notifications from server."""
params = notification.params
print(f"Progress: {params.progress}/{params.total} - {params.message}")
# Register handler
session.on_notification("notifications/progress", handle_progress)
# Call tool (progress updates will arrive via handler)
result = await session.call_tool("process_large_file", {"file_path": "/data/large.csv"})TypeScript 版本发送的是 notifications/progress 消息,参数同样是 progressToken、progress、total 和 message[1]。
取消请求:客户端停掉不需要的任务
请求取消让客户端终止已经没有意义、或者耗时过长的请求[1]。Python 工具在循环中检查 ctx.is_cancelled,一旦为真就抛出 CancelledError[1]:
if ctx.is_cancelled:
raise CancelledError("Search cancelled by user")搜索示例在取消发生时会返回已经找到的部分结果[1]。客户端一侧,超时的搜索通过发送 notifications/cancelled 消息来停止,消息里带上 requestId 和 reason[1]:
await session.send_notification({
"method": "notifications/cancelled",
"params": {"requestId": task.request_id, "reason": "Timeout"}
})资源模板:用参数拼出动态 URI
资源模板允许服务器定义带参数的动态 URI,它适合 API 和数据库这类场景[1]。一个模板包含 uriTemplate、name、description 和 mimeType 字段[1]:
ResourceTemplate(
uriTemplate="db://users/{user_id}",
name="User Profile",
description="Fetch user profile by ID",
mimeType="application/json"
),read_resource 处理函数会解析传入的 URI,把参数填进去再取数据[1]。TypeScript 示例注册的是 GitHub 问题的模板,形如 github://repos/{owner}/{repo}/issues/{issue_number}[1]。
应用生命周期:启动和关闭资源
这里的生命周期指进程的启动和关闭,不是 initialize 握手[1]。Python 示例把一个名为 lifespan 的异步上下文管理器传给 Server,启动时打开数据库连接和缓存,关闭时再释放它们[1]:
@asynccontextmanager
async def lifespan(server: Server):
"""Manage server lifecycle."""
global db_connection, cache2026-07-28 版变了什么
在 MCP 2026-07-28 版中,initialize 握手和协议层面的会话被移除。服务器进程的启动和关闭仍然由应用自己负责[1]。Tasks 现在是单独版本化的扩展:服务器可以在工具调用中返回任务句柄,客户端用 tasks/get、tasks/update 和 tasks/cancel 来驱动它[1]。2025-11-25 版的实验性 Tasks 接口不向后兼容,tasks/list 也已经不存在了[1]。
日志控制属于旧版
MCP 日志在 2026-07-28 版中被标记为弃用,并有资格在 2027 年 7 月 28 日或之后发布的第一个规范修订版中被移除[1]。旧版允许客户端调整服务器端的日志级别[1]。新服务器应使用 stdio 下的 stderr,以及 OpenTelemetry 做结构化的可观测性[1]。
错误处理:该用哪个错误码
在 McpError 之上定义自己的错误类。比如校验错误使用 ErrorCode.INVALID_PARAMS[1]:
class ValidationError(ToolError):
"""Invalid input parameters."""
def __init__(self, message: str):
super().__init__(ErrorCode.INVALID_PARAMS, message)Python 示例中,找不到资源和权限不足的错误用 ErrorCode.INVALID_REQUEST,未预料的内部错误用 ErrorCode.INTERNAL_ERROR[1]。TypeScript 示例会把已有的 McpError 原样抛出,其他错误则返回一条通用信息[1]。
工具注解只是提示
工具注解描述的是只读、破坏性、幂等或开放世界等行为[1]。它们只是提示,除非来自可信的服务器,否则不要把它们当成授权或安全保证[1]。
进度、取消和错误码怎么配合
进度通知让用户知道任务做到哪一步。示例里 total 字段给出总数,客户端可以据此算出进度比例[1]。取消时,搜索示例返回的是已经找到的部分结果,而不是直接失败[1]。出错时,参数不合法用 ErrorCode.INVALID_PARAMS,找不到资源和权限不足用 ErrorCode.INVALID_REQUEST,其他未预料的问题用 ErrorCode.INTERNAL_ERROR[1]。客户端据此可以区分是输入有问题,还是服务器内部出了错。
Tasks 和旧日志的版本边界
Tasks 是单独版本化的扩展。判断兼容性时,要看 Tasks 扩展自己的版本,而不是只看核心规范的版本号[1]。日志部分则相反,它是为兼容旧实现保留的。新服务器应使用 stderr 配合 stdio,或者用 OpenTelemetry 做结构化的观测[1]。
常见问题
还要照着日志示例写吗? 只在兼容旧实现时需要。新服务器应使用 stderr 和 OpenTelemetry[1]。
**还需要 initialize 吗?** 2026-07-28 版已经移除了这个握手,新代码不要围绕它来设计[1]。
本文改编自 Microsoft 开源课程 *MCP for Beginners* 中关于 MCP 协议特性的章节[1],原文以 MIT 许可证发布。
来源
- 1MCP Protocol Features Deep DiveMicrosoft mcp-for-beginners (MIT)
引用本页: MCP 协议特性一览:进度、取消、日志. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-protocol-features. 更新于 2026-11-14