五个被低估的MCP能力
文章介绍工具行为注解等常被忽视的 MCP 特性,说明如何让客户端识别只读、幂等、破坏性及外部访问行为。合理提供这些元数据可帮助 Agent 更安全、准确地调用工具。
完整中文译文
人人都在构建 MCP server。
大多数讨论都集中在 transports、身份验证、OAuth 和工具暴露上。这些话题确实很重要,但最近在构建几个 MCP server 时,我开始关注规范中的另一个部分——它似乎远没有得到应有的重视。
有意思的是,这些并不是什么隐藏 API。它们早已是 Model Context Protocol 规范或其官方扩展的一部分。
下面这些功能都不会改变你的工具做什么。
它们改变的是 client 能否更清楚地理解你的 server,以及如何与它交互。
1. 工具行为注解
正是这个功能启发了我写这篇文章。
MCP 允许你通过 readOnlyHint、idempotentHint、destructiveHint 和 openWorldHint 等 annotations 来描述工具的行为。
{
"annotations": {
"readOnlyHint": true,
"idempotentHint": true,
"destructiveHint": false,
"openWorldHint": false
}
}
你不仅是在告诉 client 工具能做什么,也是在告诉它这个工具会如何行动。
readOnlyHint 表示工具不会修改其运行环境。
idempotentHint 表示:使用相同参数重复调用一个写入工具,不会对环境产生额外影响。
destructiveHint 用于区分可能具有破坏性的更新与增量式更新。
openWorldHint 表示工具是否可能与开放世界中的外部实体交互。
例如,Web 搜索工具运行在开放世界中,而访问固定本地记忆存储的工具则运行在封闭领域中。
这些可能看起来只是几个简单的布尔值,但它们可以在 client 规划执行过程、向用户展示工具或设计确认流程时,提供非常有用的上下文。
这里有几个细节值得注意。
idempotentHint 和 destructiveHint 只对非只读工具有意义。此外,destructiveHint: false 表示该工具只会执行增量式更新。
最重要的是,这些只是 hints,并非安全保证。client 不应该根据来自不受信任 server 的 annotations 做出涉及安全的决策。身份验证、授权、用户同意以及确定性的安全防护依然不可或缺。
📖 MCP 规范:Tools
2. 结构化工具结果
许多 MCP 工具仍然只返回文本。
Found 12 matching documents.
对人类而言,这完全没问题。
但如果同时获得底层的结构化数据,client 往往能从中受益。
MCP 工具可以通过 structuredContent 返回一个 JSON 值。
{
"structuredContent": {
"documents": [
{
"title": "...",
"url": "...",
"lastModified": "..."
}
]
}
}
这个值不一定非要是对象。它可以是任何有效的 JSON 值,包括数组、字符串、数字、布尔值或 null。
结构化数据可以让 client 更容易验证结果、渲染界面、将数据传入其他工作流,也能让模型直接处理各个字段,而不必解析自然语言响应。
为了向后兼容,规范建议同时在普通的文本 content block 中返回序列化后的 JSON。
这里需要区分一个术语:MCP structuredContent 指的是结构化工具结果。它与 LLM structured output 或受 schema 约束的模型生成并不是一回事。
📖 MCP 规范:Structured Content
3. 输出 Schema
如果要返回结构化数据,就应该告诉 client 数据长什么样。
当 client 通过 tools/list 发现工具时,它会获得每个工具的 inputSchema。
工具还可以暴露 outputSchema。
{
"name": "search_documents",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
}
},
"required": ["query"]
},
"outputSchema": {
"type": "object",
"properties": {
"documents": {
"type": "array"
}
},
"required": ["documents"]
}
}
这意味着 client 可以在调用工具之前,先检查 structuredContent 的预期结构。
如果定义了 output schema,server 就必须返回符合该 schema 的结构化内容。同时也鼓励 client 对结果进行验证。
更可预测的 client 端处理方式
更清晰的工具文档
我喜欢这样理解:
结构化内容告诉 client 你返回了什么。输出 schema 则在返回结果之前,告诉 client 应该期待什么。
这两个功能可以很好地互相补充。
📖 MCP 规范:Output Schema
4. 进度报告
并不是所有操作都能在一秒内完成。
想象一下为整个代码仓库建立索引、处理数千个文件,或者运行一个大型 AI 工作流。
如果没有进度报告,用户可能只能盯着加载动画,却不知道任务是否仍在继续。
MCP 支持为耗时请求发送可选的进度通知。
当 client 希望接收进度更新时,它会在请求的 metadata 中加入一个唯一的 progressToken。
{
"_meta": {
"progressToken": "index-repository-123"
}
}
之后,server 可以发送与该 token 关联的 notifications/progress 消息。
{
"method": "notifications/progress",
"params": {
"progressToken": "index-repository-123",
"progress": 42,
"total": 100,
"message": "Indexing repository"
}
}
即使 client 提供了 token,server 也不一定必须发送进度通知。如果 server 决定发送,那么每次通知中的 progress 值都必须递增。当总工作量未知时,total 可以省略。
有时候,好的 UX 并不意味着让任务执行得更快。
而是让等待不再那么令人困惑。
📖 MCP 规范:Progress
5. Tasks 异步执行
有些操作不应该一直保持请求连接,直到工作全部完成。
MCP Tasks 为 CI pipelines、批处理、外部任务、审批工作流或模型训练等耗时操作提供了异步执行能力。
Tasks 是 MCP 的官方扩展,而不是核心协议的一部分。client 和 server 都必须声明自己支持这项扩展。
当 server 判断一个受支持的请求将长时间运行时,可以返回一个持久化的 task handle,而不是最终结果。
接收 task identifier
通过 tasks/get 轮询 task
重新连接后恢复轮询
通过 tasks/update 提供请求所需的输入
通过 tasks/cancel 请求取消任务
在 task 完成后获取最终结果
Tasks 还可以暴露 working、input_required、completed、failed 和 cancelled 等状态。
轮询是默认机制。如果双方支持,server 还可以额外提供 task notifications。
当底层操作可能比网络连接存活得更久,或者需要暂停并等待人工输入时,这种模型尤其有用。
📖 MCP Tasks 扩展
人们很容易把 MCP 看作一种暴露工具的协议。
但我认为它远不止如此。
它同样是一种帮助 client 理解和操作这些工具的协议。
如果一个 client 知道工具是否会修改环境、重复调用是否会产生额外影响、应该期待怎样的输出结构、如何使用结构化数据,以及如何跟踪运行时间较长的任务,那么与只获得名称和描述的 client 相比,它拥有的上下文要丰富得多。
这正是最吸引我的地方。
这个协议不仅描述了能力。
它也在描述行为和执行模式。
其中一些功能只需要少量工作就能实现。另一些功能——例如 Tasks——则需要 client 和 server 双方提供更深入的支持。
尽管如此,它们仍然值得了解,因为它们可以带来更可预测的集成效果,并为用户提供更好的体验。
如果你正在构建一个 MCP server,那么除了工具名称、描述和 input schemas 之外,值得再多花一点时间,看看还有哪些能力可以利用。
这样一来,你的 client 或许能更好地理解 server,你的用户也可能因此获得更加透明、可靠的体验。
我很想听听,在你看来,MCP 还有哪些部分值得得到更多关注。
作为后续措施,你可以考虑屏蔽此人和/或举报滥用行为。