Skip to content

第17章 Sampling:服务端发起的 LLM 调用

17.1 什么是 Sampling

在前面的章节中,我们看到的都是 Client 向 Server 发起请求的模式——调用工具、读取资源、获取提示词模板。这些都是经典的"Client 主动,Server 被动"的交互方式。

Sampling 彻底反转了这个方向。

Sampling 是 MCP 协议中唯一允许 Server 主动向 Client 发起请求的核心功能之一。 具体来说,Server 可以通过 sampling/createMessage 请求,要求 Client 使用其连接的 LLM 进行一次文本生成(completion)。Client 收到请求后,将其转发给 LLM,拿到生成结果,再返回给 Server。

这个看似简单的"反向调用"设计,解决了一个困扰 Agent 生态已久的核心问题:Server 如何在不持有 LLM API Key 的情况下使用 AI 能力?

传统方式下,如果一个 MCP Server 的逻辑中需要调用 LLM(比如对用户提交的代码进行审查、生成摘要、做分类判断),它必须自己持有一个 LLM API Key,自己管理调用、计费、限流。这带来了几个严重问题:

  1. 密钥管理负担:每个 Server 都要安全地存储和轮转 API Key
  2. 计费碎片化:用户可能同时使用多个 Server,每个 Server 各自计费,费用不透明
  3. 模型选择权丧失:Server 绑定了特定的 LLM 提供商,用户无法选择自己偏好的模型
  4. 安全风险放大:API Key 散布在各种第三方 Server 中,攻击面急剧扩大

Sampling 的设计哲学是:LLM 的调用权归 Client 所有,Server 只需要描述"我需要什么样的 AI 输出",具体用哪个模型、怎么调用、花多少钱,全部由 Client 决定。

17.2 能力声明

Sampling 不是默认启用的。Client 必须在初始化阶段通过能力声明告知 Server 自己支持 Sampling。

最基本的声明方式:

json
{
  "capabilities": {
    "sampling": {}
  }
}

如果 Client 还支持在 Sampling 过程中使用工具(这是更高级的能力),需要额外声明:

json
{
  "capabilities": {
    "sampling": {
      "tools": {}
    }
  }
}

只有当 Client 声明了 sampling 能力后,Server 才被允许发送 sampling/createMessage 请求。这是 MCP 协议一贯的能力协商原则——不要假设对方支持什么,先问再用。

值得注意的是,规范中还有一个 context 子能力(用于 includeContext 参数),但它已被标记为"软弃用"(soft-deprecated),新的实现不建议使用。

17.3 CreateMessage 请求与响应

17.3.1 请求结构

Server 通过发送 sampling/createMessage 来请求 LLM 生成。请求的核心字段包括:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "请分析这段代码的时间复杂度"
        }
      }
    ],
    "systemPrompt": "你是一位资深的算法工程师。",
    "maxTokens": 500,
    "modelPreferences": {
      "hints": [{ "name": "claude-3-sonnet" }],
      "intelligencePriority": 0.8,
      "speedPriority": 0.5
    }
  }
}

几个关键设计决策值得深入理解:

messages 数组:Server 构造一个完整的对话上下文传给 Client。消息内容支持三种类型——文本(text)、图片(image,Base64 编码)和音频(audio,Base64 编码)。这意味着 Sampling 不仅支持纯文本场景,也天然支持多模态交互。

systemPrompt:Server 可以指定系统提示词。但 Client 有权修改它——这是人机审批机制的一部分,后文会详细讨论。

maxTokens:生成的最大 token 数。这是一个硬约束,防止 Server 请求过大的生成量导致不可控的费用。

modelPreferences:这是最精妙的设计之一,我们在 17.5 节专门分析。

17.3.2 响应结构

Client 处理完请求后返回:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "role": "assistant",
    "content": {
      "type": "text",
      "text": "这段代码使用了嵌套循环,时间复杂度为 O(n^2)..."
    },
    "model": "claude-3-sonnet-20240307",
    "stopReason": "endTurn"
  }
}

model 字段告诉 Server 实际使用的是哪个模型——Server 只是"建议"了一个模型,Client 可能用了完全不同的模型。stopReason 表明生成停止的原因,常见值包括 endTurn(正常结束)和 toolUse(需要调用工具)。

17.4 Sampling 中的工具调用

Sampling 的基础用法是"Server 请求一次 LLM 生成,拿到文本结果"。但 MCP 规范进一步支持了一个更强大的模式:在 Sampling 过程中使用工具

这意味着 Server 可以定义一组工具,让 Client 侧的 LLM 在生成过程中调用这些工具,形成一个完整的 Agent 循环——全部发生在一次 Sampling 会话中。

整个流程的关键在于:工具的定义由 Server 提供,但工具的执行也由 Server 完成。Client 和 LLM 只负责"决定调用哪个工具、传什么参数",真正的执行权还在 Server 手中。

工具选择模式

Server 可以通过 toolChoice 字段控制 LLM 使用工具的行为:

模式含义
{mode: "auto"}LLM 自主决定是否使用工具(默认)
{mode: "required"}LLM 必须至少调用一个工具
{mode: "none"}禁止 LLM 使用任何工具

一个常见的实践是:Server 在多轮工具循环中设置最大迭代次数,当到达最后一轮时,传入 {mode: "none"} 强制 LLM 输出最终的文本结果,避免无限循环。

消息内容约束

工具调用引入了严格的消息格式约束,这些约束是为了兼容不同 LLM 提供商的 API 设计(如 OpenAI 的 tool 角色、Gemini 的 function 角色):

  1. tool_result 消息不能混合其他内容:当一条 user 消息包含 tool_result 类型的内容时,该消息只能包含 tool_result,不能混入 textimage
  2. tool_use 和 tool_result 必须成对出现:每个 assistant 消息中的 tool_use(带有 id)都必须在紧接其后的 user 消息中有对应的 tool_result(带有匹配的 toolUseId
  3. 支持并行工具调用:LLM 可以在一条 assistant 消息中返回多个 tool_use,Server 需要执行全部工具并一次性返回所有结果

17.5 模型偏好系统

Server 和 Client 可能使用完全不同的 LLM 提供商。一个 Server 不能简单地说"请用 claude-3-sonnet",因为 Client 可能根本没有接入 Anthropic 的 API。

MCP 用一个两层抽象解决了这个问题:hints(提示)+ priorities(优先级)

Hints:模型名称的模糊匹配

hints 是一个有序的模型名称建议列表,每个名称被当作子字符串匹配:

json
{
  "hints": [
    { "name": "claude-3-sonnet" },
    { "name": "claude" }
  ]
}

Client 按顺序尝试匹配:先看有没有包含 "claude-3-sonnet" 的模型,没有就退而求其次找包含 "claude" 的。如果都没有,Client 可以根据 priorities 选择能力最接近的替代模型。比如,一个只接入了 Google 的 Client 可能会把 "claude-3-sonnet" 映射到 gemini-1.5-pro

Hints 只是建议,不是命令。 最终的模型选择权始终在 Client 手中。

Priorities:能力维度的量化表达

三个归一化的优先级值(0 到 1),让 Server 精确表达自己的需求侧重:

优先级含义高值倾向
costPriority成本敏感度更便宜的模型
speedPriority延迟敏感度更快的模型
intelligencePriority能力需求更强的模型

一个代码审查 Server 可能设置 intelligencePriority: 0.9, speedPriority: 0.3——它需要高质量的分析,不在乎多等几秒。而一个实时聊天机器人的 Server 可能设置 speedPriority: 0.9, intelligencePriority: 0.5——响应速度比深度分析更重要。

这个设计的精妙之处在于:它把"用哪个模型"的决策完全解耦了。Server 描述需求,Client 根据自己实际可用的模型做最优匹配。未来出现新的模型、新的提供商,协议本身不需要任何修改。

17.6 人机审批:安全的核心防线

Sampling 赋予了 Server 通过 Client 调用 LLM 的能力。但这也意味着:一个恶意的 Server 可以构造精心设计的 prompt,通过用户的 Client 生成有害内容,或者通过工具调用执行危险操作。

MCP 规范对此的回答是:人机审批(Human-in-the-Loop)是必须的。

规范要求 Client 应当(SHOULD)提供以下能力:

  1. 请求审查:用户可以看到 Server 构造的完整 prompt,包括系统提示词和对话历史,并有权编辑或拒绝
  2. 响应审查:LLM 生成的内容在发回 Server 之前,用户可以查看和修改
  3. 工具调用审查:当 LLM 决定调用工具时,用户可以审批每一次工具调用

如果用户拒绝了 Sampling 请求,Client 应返回错误码 -1

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -1,
    "message": "User rejected sampling request"
  }
}

需要注意的是,规范使用的是 SHOULD 而非 MUST——这是一个务实的选择。在某些自动化场景中(比如 CI/CD 流水线中的 Agent),要求每次 Sampling 都弹出审批窗口并不现实。但规范明确建议:在面向终端用户的应用中,人机审批应该是默认行为。

17.7 安全考量

Sampling 的安全模型建立在几个层面上:

第一层:能力协商。Server 只有在 Client 明确声明支持 Sampling 后才能发起请求。Client 可以选择不支持 Sampling,从而完全规避相关风险。

第二层:人机审批。如前所述,用户对每次 Sampling 的 prompt 和结果都有审查权。

第三层:Client 的完全控制权。Client 可以:

  • 修改 Server 发来的 systemPrompt
  • 选择与 Server 建议不同的模型
  • 限制 maxTokens 的上限
  • 实施请求速率限制(rate limiting)
  • 对消息内容进行安全审查和过滤

第四层:工具调用的迭代限制。当 Sampling 中涉及工具调用时,Server 和 Client 都应实现循环次数上限,防止 LLM 陷入无限的工具调用循环。

第五层:内容验证。双方都应验证消息内容的合法性,包括:

  • tool_result 消息是否只包含 tool_result 类型
  • tool_use 和 tool_result 是否正确配对
  • 敏感数据是否被妥善处理

这些错误场景对应明确的错误码:

错误码含义
-1用户拒绝了 Sampling 请求
-32602参数无效(如缺少 tool_result、内容类型混合)

17.8 Sampling 的革命性意义

理解 Sampling 的设计,需要把它放在更大的架构图景中看。

在没有 Sampling 的世界里,MCP Server 本质上是"被动的工具提供者"——Client 问什么它答什么,Client 不问它就沉默。这限制了 Server 的能力上限:它无法实现需要"思考"的复杂逻辑。

有了 Sampling,Server 变成了"有认知能力的智能体"。它可以:

  • 自主推理:在执行复杂任务的过程中,调用 LLM 进行中间推理
  • 动态决策:根据 LLM 的判断决定下一步操作
  • 嵌套 Agent:在一个 MCP 工具调用的内部,启动一轮完整的 LLM 对话
  • 多步规划:结合工具调用和 LLM 推理,实现复杂的多步骤任务

更关键的是,这一切都不需要 Server 自己持有任何 LLM 的 API Key。一个开源社区开发者可以发布一个功能强大的 MCP Server,用户只需要用自己已有的 AI 客户端连接它,所有的 LLM 调用都走用户自己的账号。开发者贡献能力,用户提供算力,协议负责桥接。

这是 MCP 协议设计中最具前瞻性的部分之一。它把 Agent 的"认知能力"从一个需要自建的基础设施,变成了一个可以通过协议按需获取的服务。

17.9 本章小结

Sampling 是 MCP 协议中最独特的设计之一。它反转了传统的请求方向,让 Server 可以通过 Client 间接调用 LLM,实现了以下关键能力:

  1. 零密钥架构:Server 无需持有 LLM API Key,消除了密钥管理和安全风险
  2. 模型选择权归用户:通过 hints 和 priorities 的两层抽象,Server 表达需求,Client 做最终决策
  3. 工具增强的 Agent 循环:Sampling 中支持工具定义和多轮调用,Server 可以实现完整的 Agent 行为
  4. 人机审批保障安全:用户对 prompt 和响应都有审查权,防止恶意 Server 滥用
  5. 跨提供商兼容:协议设计兼顾了 Claude、OpenAI、Gemini 等主流 LLM API 的差异

下一章我们将分析 Elicitation 和 Roots——MCP 协议中另外两个重要的交互机制。

基于 VitePress 构建