前面几篇讲的都是同一个方向的能力流:客户端(Host)把工具交给模型,模型调用服务端干活。但 MCP 协议里还留着一条反方向的通道——服务端可以反过来向客户端请求能力:让客户端的模型帮自己生成内容(sampling),或者直接向用户追问信息(elicitation)。
这条反向通道设计得好是解耦利器,用不好就是灾难现场。这篇讲清楚两者的机制、适用场景和必须守住的边界。
MCP 的 sampling 是服务端向客户端发 sampling/createMessage 请求,由客户端侧的 LLM 完成生成再返回。它解决一个很实际的矛盾:服务端工具经常需要"一点智能",但不该为此绑定某个模型 API。
典型场景:一个"文档检索"server,拿到检索结果后需要提炼一段摘要再返回给模型——如果 server 自己内置 OpenAI 调用,就多了一组密钥要管理、一笔独立账单要付、一个供应商要锁定。走 sampling,摘要这步直接借用客户端正在用的模型,用户用自己的订阅付费,server 保持零模型依赖。
TypeScript SDK 里服务端的写法:
const result = await server.createMessage({
messages: [{
role: "user",
content: { type: "text", text: `用三句话概括以下检索结果:\n${docs}` },
}],
maxTokens: 300,
});三条必须守住的纪律:
elicitation 更直接:服务端通过 elicitation/create 向用户弹一个结构化表单要答案。它是"参数缺失"的正确解法。
对比一下就明白价值。没有 elicitation 时,deploy 工具缺"目标环境"参数,模型只能猜一个 production——这在真实系统里是事故级行为。有了 elicitation,server 返回一个带 enum: [staging, production] 的提问,用户点选,确定性拉满。
协议支持的字段类型刻意保持克制:string、number、boolean 和受限的 enum,本质就是一个单步表单。请求示例:
const answer = await server.elicitInput({
message: "要部署到哪个环境?",
requestedSchema: {
type: "object",
properties: {
env: { type: "string", enum: ["staging", "production"], description: "目标环境" },
},
required: ["env"],
},
});三种最划算的用法:危险操作的显式确认(删除、发布、花钱)、模型不可能知道的业务参数(内部环境名、成本中心编码)、二选一的分支决策(覆盖还是另存)。
这是本文最想强调的一条红线:elicitation 是单向的单步表单,不是把对话塞进工具的通道。
见过这样的设计:一个"订机票"工具,靠连续十几轮 elicitation 问出发地、目的地、日期、舱位、预算、是否中转……每轮都打断用户。这不是工具,这是把 CLI 交互复刻进了 Agent,彻底搞反了分工——对话本来就是 Host/模型的工作,工具的分内事应该自治完成。
判断标准很简单:
频率也一样:一个正常工具的 elicitation 应该是每次调用至多一次。需要问第二次,通常说明这个工具的职责切大了,该拆。
sampling 和 elicitation 是 MCP 里服务端向客户端"回借"能力的两个口子:sampling 借模型,请求小而明确,默认用户可见;elicitation 借用户的确定性,只问模型不可能知道的事,每次调用至多一问。两者共同的底线是尊重分工——智能和对话留在 Host 侧,工具保持自治、克制、无模型依赖。
MCP 系列到这里覆盖了协议、传输、安全、测试、可观测、部署、配置、长任务和反向通道。接下来打算写一篇系列复盘:把一个真实 MCP Server 从开发到上线的全链路决策串一遍,有兴趣可以关注。
本文是 MCP 系列的一篇,前作:《MCP 长任务三件事:进度上报、取消与重连后的状态恢复》《MCP Server 的最后一公里:配置管理与安全分发实战》。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。