Skip to main content
  • 状态(Status): Final
  • 类型(Type): Standards Track
  • 创建(Created): 2025-09-30
  • 作者(Author(s)): Olivier Chafik (@ochafik)
  • Issue: #1577
更新
  • 10 月 1 日:将 tool_choice 重命名为 toolChoice(+ "none" 值);移除了异类 stopReason "refusal""other";允许 {CreateMessageResult,SamplingMessage}.content 为单个内容或内容数组;
  • 10 月 6 日:使 ToolResultContentCallToolResult 对齐(支持图像/音频);添加了”可能的后续”章节。
  • 10 月 10 日:用简单的工具注册表更新了参考实现示例(统一 mcp 工具与工具循环工具,见下方评论),以及一个使用带工具采样 + 征询的”选择你自己的冒险”游戏。
  • 10 月 27 日:使 ToolResultContent.contentCallToolResult.content 对齐(使用 ContentBlock);添加了 ToolResultContent._meta
  • 11 月 5 日
    • stopReason 保留为开放字符串,但带有冗余的显式枚举以提高可见性
    • 移除了当 includeContext 与公告的 ClientCapabilities.sampling.context 不匹配时抛错的要求
    • 通过规定采样在较早规范版本中**不得(MUST NOT)**返回数组,缓解了 CreateMessageResult.content 是内容数组或单个内容所带来的向后兼容问题(+ 承认使用采样的代码的 SDK 更新将需要小的代码改动)
  • 11 月 7 日:将类型 ToolCallContent 重命名为 ToolUseContent(以匹配其 tool_use 类型和 toolUse stopReason)。SEP 已获批准!
  • 11 月 10 日:移除 disable_parallel_tool_use / 保留待后续更新,因为 Gemini API 目前无法实现这一点。
  • 11 月 11 日:添加了关于 Gemini API 函数调用模式和角色的额外说明;要求带工具结果内容的 SamplingMessage 不与其他内容类型混合

摘要

本 SEP 向 sampling/createMessage 引入 toolstoolChoice 参数,并软弃用 includeContext(将 thisServerallServers 置于一个能力之下)。这允许 MCP 服务器使用客户端的 token 运行自己的代理式循环(仍在用户监督之下),并降低客户端实现的复杂性(上下文支持变为显式可选)。

动机

  • 采样不支持工具调用,尽管它是现代代理式行为的基石。没有对它的显式支持,使用采样的 MCP 服务器要么尝试用复杂的提示/对输出的自定义解析来模拟工具调用,要么被局限于更简单的、非代理式的请求。添加对工具调用的支持可以在 MCP 生态中解锁许多新颖用例。
  • 上下文包含的定义含糊(见此文档):它使得完整实现采样特别棘手,这连同采样所需的其他预防措施(不受本 SEP 影响)可能导致了该特性在客户端中的低采用(该特性在 2024 年 11 月的 MCP 规范中引入)。
请注意一些相关工作:
  • MCP Sampling(@jerome3o-anthropic):极为相似的提案:
    • 添加相同的 tools 语义,
    • 弃用 includeContext(该文档解释了为何其语义含糊)
    • (更进一步建议显式上下文共享,这超出本提案范围)
  • 允许 Prompt/Sampling 消息包含多个内容块。#198
    • 在此 PR 中,我们使 {CreateMessageResult,SamplingMessage}.content 接受单个内容或内容数组。result.content 的变更向后不兼容,但支持并行工具调用所必需。SamplingMessage.content 的变更则使编写工具循环自然得多(见参考实现中的示例:toolLoopSampling.ts
在下方”可能的后续”章节中,我们给出了本 SEP 有意保持范围之外、但我们注意使本 SEP 与之合理兼容的特性示例。

规范

概览

  • CreateMessageRequest 中添加传统的工具调用支持,带 tools(含 JSON schema)和 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 时,必须抛错
    • 当定义了 tooltoolChoice 但缺少 clientCapabilities.sampling.tools 时,**必须(MUST)**抛错
    • 服务器**应当(SHOULD)**避免 includeContext != 'none',因为值 "thisServer""allServers" 可能在未来的规范版本中被移除。
    • CreateMessageRequest.messages **必须(MUST)**使任何带 ToolUseContent(和 id: $id1)的 “assistant” 消息与一条带 ToolResultContent(和 tool_result_id: $id1)的 “user” 消息相平衡
      • 注意:这是 Claude API 实现的一个要求(并行工具调用必须一次性全部得到响应)
    • 带工具结果内容块的 SamplingMessage **不得(MUST NOT)**包含其他内容类型。

Schema 变更

  • ClientCapabilities
  • CreateMessageRequest(使用既有的 Tool
    • 说明:
      • OpenAI vs. Anthropic API 避免并行工具调用的惯用法:
        • OpenAI:parallel_tool_calls: false(顶层参数)
        • Anthropic:tool_choice.disable_parallel_tool_use: true
          • 此处优先,因为未设时默认值为 false(即允许并行工具调用)
      • OpenAI vs. Anthropic API 关于 tool_choice "none" vs. tools
        • OpenAI:tools: [$Foo], tool_choice: "none" 禁止任何工具调用
          • 此处优先采用的行为
        • Anthropic:tools: [$Foo], tool_choice: {mode: "none"} 仍可能调用工具 Foo
      • Gemini vs. OAI / Anthropic 关于 disable_parallel_tool_use
        • Gemini API 目前无法禁用并行工具调用(不像 OAI / Anthropic API)。暂时移除此标志,待 Gemini 有任何支持方式时再重新引入。否则客户端会得到意外的多个工具调用(或者若那样实现,则意外失败/代价高昂地重试直到发出单个工具调用)
        • Gemini API 的函数调用模式有一个 ANY 值,应当与所提议的 required 匹配
  • SamplingMessage
  • 说明:
    • 在工具调用方面,各 API 之间角色 vs. 内容类型的差异:
      • 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,并在需要时用它们来重排工具结果。
  • CreateMessageResult
    • 说明:
      • 向后兼容问题:将 CreateMessageResult.content 作为内容数组或单个内容返回是有问题的,因此我们提议:
        • 在 2025 年 11 月规范版本之前,sampling/createMessage **不得(MUST NOT)**在 CreateMessageResult.content 中返回数组。
          • 这保证了线路层的向后兼容
        • 使用采样的既有代码可能随新 SDK 发布而破坏,因为它需要测试 content 以知道它是数组还是单个块,并相应处理。
        • 这似乎合理(?)
      • CreateMessageResult.stopReason 字段当前定义为开放 string,规范只提到 endTurn 作为示例值。
      • OpenAI vs. Anthropic API 惯用法
        • 完成/停止原因
          • OpenAI 的 ChatCompletionfinish_reason: "stop" | "length" | "tool_use"(……?)
          • Anthropicstop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal"

可能的后续

以下超出本 SEP 范围,但我们注意不排除它们,因此在适当之处,我们给出它们如何能在本 SEP 之上/之后实现的示例。

流式支持

见:流式传输工具使用结果 #117 这对某些较长时间运行的用例,或当延迟重要时可能很重要,但与 MCP 工具中的流式支持配合会更好。 一种可能的实现方式是使用带载荷的通知,并可能创建一个新方法 sampling/createMessageStreamed。两者都应与本 SEP 正交(但我们需要为结果创建 delta 类型,类似于 Claude API 和 OpenAI API 等推理 API 中的流式 API)。

缓存友好性更新

此处需要两点:
  • 引入缓存感知
    • 以 SHOULD 措辞的隐式缓存指南
    • 如 Claude API 中的显式缓存点和 TTL 语义?(包括更长缓存的 beta 行为)
      • 优点:*对至少 1 个实现者(Anthropic)*易于实现
      • 缺点:如果对其他人难以实现,则不太可能获批。
    • 如 OpenAI API 中带显式键的”整个提示”/提示前缀缓存?
      • 优点:
        • 对用户更简单(无需考虑共享前缀在哪里停止)
        • 隐式支持更新缓存(甚至可能作为子树)
      • 缺点:可能更难实现/存储效率更低
  • 引入 allowed_tools 特性,以在不破坏上下文缓存的情况下启用/禁用工具

允许客户端在代理式循环中自行调用服务器的工具

从服务器的角度看,那将消除自行调用工具/在后续采样调用中注入工具结果的需要。 MCP 服务器只需在采样请求中将其自身工具列入允许列表,用一个专门的工具定义,例如:
优点:
  • 安全,仅限于该服务器的工具。
  • 如果我们传播 mcp-session-id,可以利用并保持任何服务器端会话上下文/缓存

允许客户端在代理式循环中自行调用任何其他 MCP 服务器的工具

尽管这听起来与前一个(只允许同一服务器的工具)相似,但此选项无需协议变更/可完全由客户端作为其采样支持的实现细节完成。 终端用户将来自任何其他 MCP 服务器的工具列入允许列表以用于采样请求,而无需服务器请求任何东西。客户端 UI 例如可以将工具选择 UI 作为采样批准流的一部分显示,默认自动启用来自同一服务器的工具。 优点:
  • 技术上无需规范变更(如果有的话,将此作为客户端拥有的一种自由提及)
  • 可能类似于 CreateMessageRequest.params.includeContext = thisServer / allServers 预期语义可能意味着的东西
    • CreateMessageRequest.params.allowImplicitToolCalls = "none" | "thisServer" | "allServers" (假设我们想给服务器对此的任何控制权)
缺点:
  • 可能需要分类器以避免隐私泄露/滥用的高潜在风险
    • 如果用户误批准 Gmail MCP 工具的使用/委托,服务器就能通过采样访问其私密邮件

允许服务器列出并调用客户端的工具(客户端/服务器 → p2p)

如果我们说客户端现在可以暴露服务器能调用的工具,就打开了一系列可能性:
  • 客户端可以”转发”其他服务器的工具(也许带一些命名空间以实现无缝聚合)
    • 服务器随后可以将这些工具作为其工具循环的一部分调用。
  • 客户端和服务器语义开始失去分量,我们进入一种更点对点、对称的关系
    • 客户端也可以顺便请求服务器进行采样
    • 协议层的对称性,但传输层仍有方向性(例如对 HTTP 传输,POST 请求的方向仍然重要)

简化结构化输出用例

采样的一个主要用例是获取符合给定 schema 的输出。 这在例如 OpenAI 的 API 中是可能的。 最常见的变通办法是给出单个工具并设置 tool_choice: "required",这保证输出是一个 ToolCall,其中包含符合该工具输入 schema 的输入。 虽然本 SEP 提议我们启用这种基于 "required" 的变通办法,但作为后续,最好能提供更显式/更简单的 JSON schema 支持,这也将允许工具输入中不被允许的 schema 类型(工具输入要求一个带属性的对象,因此必须为其输出至少选一个名称,这需要思考/与提示策略相互作用):