第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 能力?

graph LR
    subgraph 传统方式
        S1[MCP Server] -->|需要 API Key| LLM1[LLM API]
    end
    subgraph Sampling 方式
        S2[MCP Server] -->|sampling/createMessage| C[MCP Client]
        C -->|已有 API Key| LLM2[LLM API]
    end
    style S1 fill:#f96,stroke:#333
    style S2 fill:#6f9,stroke:#333
    style C fill:#69f,stroke:#333

传统方式下,如果一个 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。

最基本的声明方式:

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

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

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

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

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

17.3 CreateMessage 请求与响应

17.3.1 请求结构

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

{
  "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 处理完请求后返回:

{
  "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 会话中。

sequenceDiagram
    participant Server as MCP Server
    participant Client as MCP Client
    participant User as 用户
    participant LLM as LLM

    Note over Server,Client: 第一轮:发起带工具的 Sampling 请求
    Server->>Client: sampling/createMessage<br/>(messages + tools)

    Client->>User: 展示请求,等待审批
    User-->>Client: 批准

    Client->>LLM: 转发请求(附带工具定义)
    LLM-->>Client: 返回 tool_use 响应<br/>(stopReason: "toolUse")

    Client->>User: 展示工具调用,等待审批
    User-->>Client: 批准工具调用
    Client-->>Server: 返回 tool_use 结果

    Note over Server: 执行工具(如查询天气 API)
    Server->>Server: 运行 get_weather("北京")

    Note over Server,Client: 第二轮:携带工具结果继续对话
    Server->>Client: sampling/createMessage<br/>(完整历史 + tool_result + tools)

    Client->>User: 展示继续请求
    User-->>Client: 批准

    Client->>LLM: 转发(含工具结果)
    LLM-->>Client: 最终文本响应<br/>(stopReason: "endTurn")

    Client->>User: 展示最终响应
    User-->>Client: 批准
    Client-->>Server: 返回最终结果

整个流程的关键在于:工具的定义由 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 是一个有序的模型名称建议列表,每个名称被当作子字符串匹配:

{
  "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)是必须的。

sequenceDiagram
    participant Server as MCP Server
    participant Client as MCP Client
    participant User as 用户
    participant LLM as LLM

    Server->>Client: sampling/createMessage

    rect rgb(255, 240, 230)
        Note over Client,User: 审批点 1:审查请求
        Client->>User: 展示 Server 的 prompt<br/>允许用户查看和编辑
        User-->>Client: 批准 / 修改 / 拒绝
    end

    Client->>LLM: 转发(可能已被用户修改)
    LLM-->>Client: 返回生成结果

    rect rgb(230, 255, 230)
        Note over Client,User: 审批点 2:审查响应
        Client->>User: 展示 LLM 生成的内容<br/>允许用户审查和编辑
        User-->>Client: 批准 / 修改 / 拒绝
    end

    Client-->>Server: 返回(可能已被用户修改的)结果

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

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

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

{
  "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 协议中另外两个重要的交互机制。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com