> ## Documentation Index
> Fetch the complete documentation index at: https://mcp.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SEP-1577：带工具的采样

> 带工具的采样

<div className="flex items-center gap-2 mb-4">
  <Badge color="green" shape="pill">
    最终版
  </Badge>

  <Badge color="gray" shape="pill">
    标准轨道
  </Badge>
</div>

| 字段       | 值                                                                               |
| -------- | ------------------------------------------------------------------------------- |
| **SEP**  | 1577                                                                            |
| **标题**   | 带工具的采样                                                                          |
| **状态**   | 最终版                                                                             |
| **类型**   | 标准轨道                                                                            |
| **创建日期** | 2025-09-30                                                                      |
| **作者**   | Olivier Chafik ([@ochafik](https://github.com/ochafik))                         |
| **赞助方**  | 无                                                                               |
| **PR**   | [#1577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1577) |

***

## 摘要

本 SEP 引入了 `tools` 和 `toolChoice` 参数到 `sampling/createMessage`，并软弃用 `includeContext`（将 `thisServer` 和 `allServers` 置于某项能力之下进行限制）。这使得 MCP 服务器能够使用客户端的 token 运行自己的代理循环（仍在用户监督之下），并降低了客户端实现的复杂性（上下文支持变为显式可选）。

## 动机

* [采样](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) 不支持工具调用，尽管它是现代代理行为的基石。如果没有明确的支持，使用采样的 MCP 服务器要么尝试通过复杂的提示/自定义解析输出来模拟工具调用，要么仅限于更简单的非代理请求。添加工具调用支持可以解锁 MCP 生态系统中的许多新颖用例。

* 上下文包含定义模糊（参见 [本文档](https://docs.google.com/document/d/1KUsloHpsjR4fdXdJuofb9jUuK0XWi88clbRm9sWE510/edit?tab=t.0#heading=h.edw7oyac2e87)）：这使得完全实现采样变得特别棘手，连同采样所需的其他预防措施（不受本 SEP 影响），可能导致了 [客户端中该功能的采用率较低](https://modelcontextprotocol.io/clients#feature-support-matrix)（该功能是在 2024 年 11 月的 MCP 规范中引入的）。

请注意一些相关工作：

* [MCP 采样](https://docs.google.com/document/d/1KUsloHpsjR4fdXdJuofb9jUuK0XWi88clbRm9sWE510/edit?tab=t.0#heading=h.5diekssgi3pq) (@jerome3o-anthropic)：极其相似的提案：
  * 添加相同的工具语义，
  * 弃用 `includeContext`（文档解释了为何其语义模糊）
  * （进一步建议显式上下文共享，但这超出了本提案的范围）
* [允许 Prompt/采样消息包含多个内容块。#198](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/198)
  * 在此 PR 中，我们使 `{CreateMessageResult,SamplingMessage}.content` 接受单个内容或内容数组。`result.content` 的变更是向后不兼容的，但对于支持并行工具调用是必需的。`SamplingMessage.content` 的变更使得编写工具循环更加自然（参见参考实现中的示例：[toolLoopSampling.ts](https://github.com/modelcontextprotocol/typescript-sdk/blob/ochafik/sep1577/src/examples/server/toolLoopSampling.ts)）

在下方的“后续可能的步骤”部分中，我们列举了一些未纳入本 SEP 范围但我们在设计本 SEP 时已确保合理兼容的功能示例。

## 规范

### 概述

* 在 [CreateMessageRequest](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) 中添加传统工具调用支持，包含 `tools`（带 JSON 架构）和 `toolChoice` 参数，需要服务器端工具循环
  * 采样现在可以产生 ToolCallBlock 响应
  * 服务器需要自行调用工具
  * 服务器再次调用采样并传入 ToolResultParamBlock 以注入工具结果
  * `toolChoice.mode` 可以是 `"auto" | "required" | "none"` 以允许常见的结构化输出用例（见下文可能的后续改进）
  * 由新能力限制（`sampling { tools {} }`）
* 修复/更新 [CreateMessageResult](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessageresult) 中未明确指定的字符串：
  * `stopReason: "endTurn" | "stopSequence" | "toolUse" | "maxToken" | string`（显式枚举 + 开放字符串以兼容）
  * `role: "assistant"`
* 软弃用 [CreateMessageRequest.params.includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) != '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](https://modelcontextprotocol.io/specification/2025-06-18/schema#clientcapabilities)

  ```typescript theme={null}
  interface ClientCapabilities {
    ...
    sampling?: {
      context?: object; // 新增：允许 CreateMessageRequest.params.includeContext != "none"
      tools?: object;   // 新增：允许 CreateMessageRequest.params.{tools,toolChoice}
    };
  }
  ```

* [CreateMessageRequest](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) (使用现有 [Tool](https://modelcontextprotocol.io/specification/2025-06-18/schema#tool))

  ```typescript theme={null}
  interface CreateMessageRequest {
   method: "sampling/createMessage";
   params: {
     ...
     messages: SamplingMessage[]; // 注意：类型已更新，见下文
     
     tools?: Tool[] // 新增（现有类型）

     toolChoice?: ToolChoice // 新增
   };
  }

  interface ToolChoice { // 新增
    mode?: "auto" | "required" | "none";
    // disable_parallel_tool_use?: boolean; // 更新（11 月 10 日）：已移除，见下文
  }
  ```

  * 注意：
    * 避免并行工具调用的 OpenAI 与 Anthropic API 习惯用法：
      * OpenAI: `parallel_tool_calls: false` (顶层参数)
      * Anthropic: `tool_choice.disable_parallel_tool_use: true`
        * 此处首选，因为如果未设置，默认值为 false（即允许并行工具调用）
    * 关于 `tool_choice` `"none"` 与 `tools` 的 OpenAI 与 Anthropic API 对比：
      * OpenAI: `tools: [$Foo], tool_choice: "none"` 禁止任何工具调用
        * 此处首选行为
      * Anthropic: `tools: [$Foo], tool_choice: {mode: "none"}` 仍可能调用工具 `Foo`
    * 关于 `disable_parallel_tool_use` 的 Gemini 与 OAI / Anthropic 对比：
      * Gemini API 目前无法禁用并行工具调用（不同于 OAI / Anthropic API）。暂时移除此标志，待 Gemini 支持后再引入。否则客户端会收到意外的多个工具调用（或者如果那样实现，会导致意外失败/代价高昂的重试直到发出单个工具调用）
      * Gemini API 的 [函数调用模式](https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#function_calling_modes) 有一个 `ANY` 值，应匹配提议的 `required`

* [SamplingMessage](https://modelcontextprotocol.io/specification/2025-06-18/schema#samplingmessage):

  ```typescript theme={null}
  /*
    之前：
    
    interface SamplingMessage {
      content: TextContent | ImageContent | AudioContent
      role: Role;
    }
  */

  type SamplingMessage = UserMessage | AssistantMessage; // 新增

  type AssistantMessageContent =
    | TextContent
    | ImageContent
    | AudioContent
    | ToolUseContent;
  type UserMessageContent =
    | TextContent
    | ImageContent
    | AudioContent
    | ToolResultContent;
  interface AssistantMessage {
    // 新增
    role: "assistant";
    content: AssistantMessageContent | AssistantMessageContent[];
  }

  interface ToolUseContent {
    // 新增
    type: "tool_use";
    name: string;
    id: string;
    input: object;
  }

  interface UserMessage {
    // 新增
    role: "user";
    content: UserMessageContent | UserMessageContent[];
  }

  interface ToolResultContent {
    // 新增
    _meta?: { [key: string]: unknown };
    type: "tool_result";
    toolUseId: string;
    content: ContentBlock[];
    structuredContent: object;
    isError?: boolean;
  }
  ```

* 注意：
  * 关于工具调用时 role 与 content 类型在不同 API 之间的差异：
    * OpenAI: `role: "system" | "user" | "assistant" | "tool"`（其中 tool 用于工具结果），而工具调用嵌套在 assistant 消息中，content 通常为 null，但一些"OpenAI 兼容"API 接受非 null 值
      * ```typescript theme={null}
        [
          { role: "user", content: "what is the temperature in london?" },
          {
            role: "assistant",
            content: "Let me use a tool...",
            tool_calls: [
              {
                id: "call_1",
                type: "function",
                function: {
                  name: "get_weather",
                  arguments: '{"location": "London"}',
                },
              },
            ],
          },
          {
            role: "tool",
            content: '{"temperature": 20, "condition": "sunny"}',
            tool_call_id: "call_1",
          },
        ];
        ```
    * Claude API: `role: "user" | "assistant"`，工具使用和结果通过特殊类型的消息内容部分传递：
      * ```typescript theme={null}
        [
          {
            "role": "user",
            "content": [
              {
                "type": "text",
                "text": "what is the temperature in london?"
              }
            ],
          {
            "role": "assistant",
            "content": [
              {
                "type": "text",
                "text": "Let me use a tool..."
              },
              {
                "type": "tool_use",
                "id": "call_1",
                "name": "get_weather",
                "input": {"location": "London"}
              }
            ]
          },
          {
            "role": "user",
            "content": [
              {
                "type": "tool_result",
                "tool_call_id": "call_1",
                "content": {"temperature": 20, "condition": "sunny"}
              }
            ]
          }
        ]
        ```
    * Gemini API:
      * `function` 角色（类似于 OAI 的 `tool` 角色）
      * 无工具调用 id 概念（[函数调用](https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#parallel_function_calling)：Gemini 要求工具结果的提供顺序与工具使用部分完全一致。实现可以生成工具调用 id 并在需要时使用它们重新排序工具结果。

* [CreateMessageResult](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessageresult)

  ```typescript theme={null}
  /*
    之前：

    interface CreateMessageResult {
      _meta?: { [key: string]: unknown };
      content: TextContent | ImageContent | AudioContent;
      role: Role;
      stopReason?: string;
      [key: string]: unknown;
  }
  */
  interface CreateMessageResult {
    _meta?: { [key: string]: unknown };

    content: AssistantMessageContent | AssistantMessageContent[] // 已更新

    role: "assistant"; // 已更新

    stopReason?: "endTurn" | "stopSequence" | "toolUse" | "maxToken" | string // 已更新

    [key: string]: unknown;
  }
  ```

  * 注意：
    * 向后兼容性问题：将 CreateMessageResult.content 作为内容数组或单个内容返回是有问题的，因此我们建议：
      * 在规范版本 2025 年 11 月之前，`sampling/createMessage` 不得在 `CreateMessageResult.content` 中返回数组。
        * 这保证了传输级别的向后兼容性
      * 使用采样的现有代码可能会在新的 SDK 版本中中断，因为它需要测试 content 是数组还是单个块，并相应处理。
      * 这似乎是合理的 (?)
    * `CreateMessageResult.stopReason` 字段目前定义为开放 `string`，规范仅提及 `endTurn` 作为示例值。
    * OpenAI 与 Anthropic API 习惯用法
      * 完成/停止原因
        * OpenAI 的 [ChatCompletion](https://platform.openai.com/docs/api-reference/chat/object): `finish_reason: "stop" | "length" | "tool_use"` (…?)
        * [Anthropic](https://docs.claude.com/en/api/handling-stop-reasons): `stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal"`

## 可能的后续步骤

这些不在本 SEP 的范围内，但已注意不排除它们，因此在适当的地方我们给出了如何在本 SEP 之上/之后实现它们的示例。

### 流式支持

参见：[流式工具使用结果 #117](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/117)

这对于某些运行时间较长的用例或当延迟很重要时可能很重要，但如果能与 MCP 工具中的流式支持配合会更好。

实现这一点的一种可能方法是使用带负载的通知，并可能创建一个新方法 `sampling/createMessageStreamed`。这两者都应与本 SEP 正交（但我们需要为结果创建增量类型，类似于推理 API 中的流式 API，如 Claude API 和 OpenAI API）。

### 缓存友好性更新

这里需要两点：

* 引入缓存感知
  * 隐含的缓存指南，表述为 SHOULD（应该）
  * 显式缓存点和 TTL 语义 [如 Claude API 中所示](https://docs.claude.com/en/docs/build-with-claude/prompt-caching)?（包括更长缓存的测试行为）
    * 优点：易于实现 *对于至少 1 个实现者（Anthropic）*
    * 缺点：如果对其他实现者来说难以实现，则不太可能获得批准。
  * “整个提示词”/ 带有显式键的提示词前缀缓存 [如 OpenAI API 中所示](https://platform.openai.com/docs/api-reference/responses/create#responses-create-prompt_cache_key)?
    * 优点：
      * 对用户更简单（无需考虑共享前缀在哪里停止）
      * 隐式支持更新缓存（甚至可能作为子树）
    * 缺点：可能更难实现 / 存储效率更低
* 引入 allowed\_tools 功能以启用/禁用工具而不破坏上下文缓存
  * 与本 SEP 相关，因为我们可能希望将此功能合并 [到 tool\_choice 字段下，类似于 OpenAI 的做法](https://platform.openai.com/docs/guides/function-calling)。

    ```typescript theme={null}
    interface ToolChoice { // 新增
      mode?: "auto" | "required";
      allowed_tools?: string[]
    }
    ```

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

从服务器的角度来看，这将消除自行调用工具/在后续采样调用中注入工具结果的需要。

MCP 服务器只需在采样请求中允许列出其自己的工具，使用专用的工具定义，例如：

```typescript theme={null}
{
  type: "server-tool"; // 来自同一服务器的 MCP 工具。
  name: string;
}
```

优点：

* 安全，仅限于该服务器的工具。
* 如果我们传播 mcp-session-id，可以利用保持任何服务器端会话上下文/缓存

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

虽然这听起来与上一个类似（仅允许同一服务器的工具），但此选项不需要协议更改/可以由客户端完全作为其采样支持的实现细节来完成。

最终用户将允许列出来自任何其他 MCP 服务器的工具以供采样请求使用，而无需服务器请求任何内容。客户端 UI 例如可以在采样批准流程中显示工具选择 UI，默认自动启用来自同一服务器的工具。

优点：

* 技术上不需要规范更改（如果有的话，提及这是客户端拥有的自由）
* 可能类似于 [CreateMessageRequest.params.includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) = thisServer / allServers 预期的语义可能意味着
  * `CreateMessageRequest.params.allowImplicitToolCalls = "none" | "thisServer" | "allServers"`
    （假设我们想给服务器任何控制权）

缺点：

* 可能需要分类器以避免高潜在的隐私泄露/滥用
  * 如果用户错误地批准了 Gmail MCP 工具使用/委托，服务器可以通过采样访问他们的私人电子邮件

### 允许服务器列出和调用客户端的工具（客户端/服务器 → p2p）

如果我们说客户端现在可以暴露服务器可以调用的工具，它开启了一系列可能性：

* 客户端可以“转发”其他服务器的工具（可能带有一些命名空间以实现无缝聚合）
  * 然后服务器可以在其工具循环中调用这些工具。
* 客户端和服务器语义开始失去权重，我们进入更点对点、对称的关系
  * 客户端也可以在此过程中向服务器请求采样
  * 协议层的对称性，但传输层仍有方向性（例如对于 HTTP 传输，POST 请求的方向仍然很重要）

### 简化结构化输出用例

采样的一个主要用例是获得符合给定模式的输出。

例如，这在 [OpenAI 的 API](https://platform.openai.com/docs/guides/structured-outputs) 中是可能的。

最常见的变通方法是提供单个工具并设置 `tool_choice: "required"`，这保证输出是一个包含符合工具输入模式的输入的 ToolCall。

虽然本 SEP 提议我们启用这种基于 `"required"` 的变通方法，但作为后续步骤，最好提供更明确/更简单的 JSON 模式支持，这也允许工具输入中不允许的模式类型（这需要带有属性的对象，因此必须至少为输出选择一个名称，这需要思考/与提示策略的互动）：

```typescript theme={null}
interface CreateMessageRequest {
  method: "sampling/createMessage";
  params: {
    messages: SamplingMessage[];
    ...
    format: {
      type: "json_schema",
      "schema": {
        "type": "array",
        "minItems": 5,
        "maxItems": 100
      }
    }
  }
```
