最终版标准轨道
摘要
本 SEP 引入了tools 和 toolChoice 参数到 sampling/createMessage,并软弃用 includeContext(将 thisServer 和 allServers 置于某项能力之下进行限制)。这使得 MCP 服务器能够使用客户端的 token 运行自己的代理循环(仍在用户监督之下),并降低了客户端实现的复杂性(上下文支持变为显式可选)。
动机
- 采样 不支持工具调用,尽管它是现代代理行为的基石。如果没有明确的支持,使用采样的 MCP 服务器要么尝试通过复杂的提示/自定义解析输出来模拟工具调用,要么仅限于更简单的非代理请求。添加工具调用支持可以解锁 MCP 生态系统中的许多新颖用例。
- 上下文包含定义模糊(参见 本文档):这使得完全实现采样变得特别棘手,连同采样所需的其他预防措施(不受本 SEP 影响),可能导致了 客户端中该功能的采用率较低(该功能是在 2024 年 11 月的 MCP 规范中引入的)。
- MCP 采样 (@jerome3o-anthropic):极其相似的提案:
- 添加相同的工具语义,
- 弃用
includeContext(文档解释了为何其语义模糊) - (进一步建议显式上下文共享,但这超出了本提案的范围)
- 允许 Prompt/采样消息包含多个内容块。#198
- 在此 PR 中,我们使
{CreateMessageResult,SamplingMessage}.content接受单个内容或内容数组。result.content的变更是向后不兼容的,但对于支持并行工具调用是必需的。SamplingMessage.content的变更使得编写工具循环更加自然(参见参考实现中的示例:toolLoopSampling.ts)
- 在此 PR 中,我们使
规范
概述
- 在 CreateMessageRequest 中添加传统工具调用支持,包含
tools(带 JSON 架构)和toolChoice参数,需要服务器端工具循环- 采样现在可以产生 ToolCallBlock 响应
- 服务器需要自行调用工具
- 服务器再次调用采样并传入 ToolResultParamBlock 以注入工具结果
toolChoice.mode可以是"auto" | "required" | "none"以允许常见的结构化输出用例(见下文可能的后续改进)- 由新能力限制(
sampling { tools {} })
- 修复/更新 CreateMessageResult 中未明确指定的字符串:
stopReason: "endTurn" | "stopSequence" | "toolUse" | "maxToken" | string(显式枚举 + 开放字符串以兼容)role: "assistant"
- 软弃用 CreateMessageRequest.params.includeContext != ‘none’(现在由能力限制)
- 激励无上下文采样实现
协议变更
sampling/createMessage当includeContext为"thisServer" | "allServers"但clientCapabilities.sampling.context缺失时必须抛出错误- 当定义了
tool或toolChoice但clientCapabilities.sampling.tools缺失时必须抛出错误 - 服务器应避免
[includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest)!= ‘none’,因为值"thisServer"和"allServers"可能会在未来的规范版本中被移除。 CreateMessageRequest.messages必须平衡任何带有ToolUseContent(及id: $id1)的 “assistant” 消息与带有 ToolResultContent(及tool_result_id: $id1)的 “user” 消息- 注意:这是 Claude API 实现的要求(并行工具调用必须一次性全部响应)
- 带有工具结果内容块的 SamplingMessage 不得包含其他内容类型。
架构变更
-
ClientCapabilities
-
CreateMessageRequest (使用现有 Tool)
- 注意:
- 避免并行工具调用的 OpenAI 与 Anthropic API 习惯用法:
- OpenAI:
parallel_tool_calls: false(顶层参数) - Anthropic:
tool_choice.disable_parallel_tool_use: true- 此处首选,因为如果未设置,默认值为 false(即允许并行工具调用)
- OpenAI:
- 关于
tool_choice"none"与tools的 OpenAI 与 Anthropic API 对比:- OpenAI:
tools: [$Foo], tool_choice: "none"禁止任何工具调用- 此处首选行为
- Anthropic:
tools: [$Foo], tool_choice: {mode: "none"}仍可能调用工具Foo
- OpenAI:
- 关于
disable_parallel_tool_use的 Gemini 与 OAI / Anthropic 对比:- Gemini API 目前无法禁用并行工具调用(不同于 OAI / Anthropic API)。暂时移除此标志,待 Gemini 支持后再引入。否则客户端会收到意外的多个工具调用(或者如果那样实现,会导致意外失败/代价高昂的重试直到发出单个工具调用)
- Gemini API 的 函数调用模式 有一个
ANY值,应匹配提议的required
- 避免并行工具调用的 OpenAI 与 Anthropic API 习惯用法:
- 注意:
-
SamplingMessage:
-
注意:
- 关于工具调用时 role 与 content 类型在不同 API 之间的差异:
- OpenAI:
role: "system" | "user" | "assistant" | "tool"(其中 tool 用于工具结果),而工具调用嵌套在 assistant 消息中,content 通常为 null,但一些”OpenAI 兼容”API 接受非 null 值 -
- Claude API:
role: "user" | "assistant",工具使用和结果通过特殊类型的消息内容部分传递: -
- Gemini API:
function角色(类似于 OAI 的tool角色)- 无工具调用 id 概念(函数调用:Gemini 要求工具结果的提供顺序与工具使用部分完全一致。实现可以生成工具调用 id 并在需要时使用它们重新排序工具结果。
- OpenAI:
- 关于工具调用时 role 与 content 类型在不同 API 之间的差异:
-
CreateMessageResult
- 注意:
- 向后兼容性问题:将 CreateMessageResult.content 作为内容数组或单个内容返回是有问题的,因此我们建议:
- 在规范版本 2025 年 11 月之前,
sampling/createMessage不得在CreateMessageResult.content中返回数组。- 这保证了传输级别的向后兼容性
- 使用采样的现有代码可能会在新的 SDK 版本中中断,因为它需要测试 content 是数组还是单个块,并相应处理。
- 这似乎是合理的 (?)
- 在规范版本 2025 年 11 月之前,
CreateMessageResult.stopReason字段目前定义为开放string,规范仅提及endTurn作为示例值。- OpenAI 与 Anthropic API 习惯用法
- 完成/停止原因
- OpenAI 的 ChatCompletion:
finish_reason: "stop" | "length" | "tool_use"(…?) - Anthropic:
stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal"
- OpenAI 的 ChatCompletion:
- 完成/停止原因
- 向后兼容性问题:将 CreateMessageResult.content 作为内容数组或单个内容返回是有问题的,因此我们建议:
- 注意:
可能的后续步骤
这些不在本 SEP 的范围内,但已注意不排除它们,因此在适当的地方我们给出了如何在本 SEP 之上/之后实现它们的示例。流式支持
参见:流式工具使用结果 #117 这对于某些运行时间较长的用例或当延迟很重要时可能很重要,但如果能与 MCP 工具中的流式支持配合会更好。 实现这一点的一种可能方法是使用带负载的通知,并可能创建一个新方法sampling/createMessageStreamed。这两者都应与本 SEP 正交(但我们需要为结果创建增量类型,类似于推理 API 中的流式 API,如 Claude API 和 OpenAI API)。
缓存友好性更新
这里需要两点:- 引入缓存感知
- 隐含的缓存指南,表述为 SHOULD(应该)
- 显式缓存点和 TTL 语义 如 Claude API 中所示?(包括更长缓存的测试行为)
- 优点:易于实现 对于至少 1 个实现者(Anthropic)
- 缺点:如果对其他实现者来说难以实现,则不太可能获得批准。
- “整个提示词”/ 带有显式键的提示词前缀缓存 如 OpenAI API 中所示?
- 优点:
- 对用户更简单(无需考虑共享前缀在哪里停止)
- 隐式支持更新缓存(甚至可能作为子树)
- 缺点:可能更难实现 / 存储效率更低
- 优点:
- 引入 allowed_tools 功能以启用/禁用工具而不破坏上下文缓存
-
与本 SEP 相关,因为我们可能希望将此功能合并 到 tool_choice 字段下,类似于 OpenAI 的做法。
-
与本 SEP 相关,因为我们可能希望将此功能合并 到 tool_choice 字段下,类似于 OpenAI 的做法。
允许客户端在代理循环中自行调用服务器的工具
从服务器的角度来看,这将消除自行调用工具/在后续采样调用中注入工具结果的需要。 MCP 服务器只需在采样请求中允许列出其自己的工具,使用专用的工具定义,例如:- 安全,仅限于该服务器的工具。
- 如果我们传播 mcp-session-id,可以利用保持任何服务器端会话上下文/缓存
允许客户端在代理循环中自行调用任何其他 MCP 服务器的工具
虽然这听起来与上一个类似(仅允许同一服务器的工具),但此选项不需要协议更改/可以由客户端完全作为其采样支持的实现细节来完成。 最终用户将允许列出来自任何其他 MCP 服务器的工具以供采样请求使用,而无需服务器请求任何内容。客户端 UI 例如可以在采样批准流程中显示工具选择 UI,默认自动启用来自同一服务器的工具。 优点:- 技术上不需要规范更改(如果有的话,提及这是客户端拥有的自由)
- 可能类似于 CreateMessageRequest.params.includeContext = thisServer / allServers 预期的语义可能意味着
CreateMessageRequest.params.allowImplicitToolCalls = "none" | "thisServer" | "allServers"(假设我们想给服务器任何控制权)
- 可能需要分类器以避免高潜在的隐私泄露/滥用
- 如果用户错误地批准了 Gmail MCP 工具使用/委托,服务器可以通过采样访问他们的私人电子邮件
允许服务器列出和调用客户端的工具(客户端/服务器 → p2p)
如果我们说客户端现在可以暴露服务器可以调用的工具,它开启了一系列可能性:- 客户端可以“转发”其他服务器的工具(可能带有一些命名空间以实现无缝聚合)
- 然后服务器可以在其工具循环中调用这些工具。
- 客户端和服务器语义开始失去权重,我们进入更点对点、对称的关系
- 客户端也可以在此过程中向服务器请求采样
- 协议层的对称性,但传输层仍有方向性(例如对于 HTTP 传输,POST 请求的方向仍然很重要)
简化结构化输出用例
采样的一个主要用例是获得符合给定模式的输出。 例如,这在 OpenAI 的 API 中是可能的。 最常见的变通方法是提供单个工具并设置tool_choice: "required",这保证输出是一个包含符合工具输入模式的输入的 ToolCall。
虽然本 SEP 提议我们启用这种基于 "required" 的变通方法,但作为后续步骤,最好提供更明确/更简单的 JSON 模式支持,这也允许工具输入中不允许的模式类型(这需要带有属性的对象,因此必须至少为输出选择一个名称,这需要思考/与提示策略的互动):