MCP 的常见讨论集中在客户端调用服务器的 tools/call;但在 2025-06-18 规范里,服务器还可以在同一条会话内向客户端发起 sampling/createMessage(向宿主 LLM 要生成)与 elicitation/create(向用户要结构化输入)。这两类消息与 tools/call 一样走 JSON-RPC,却把模型密钥、计费主体、隐私与审批 UI 全部压在客户端一侧。若 Agent 网关只做「转发 tools/list」而不实现反向 RPC 的状态机,远程 MCP 在工具执行中途索要采样或弹窗时,轻则 JSON-RPC 挂起直至超时,重则用户在无上下文的情况下批准一段未审查的 prompt。本文只依据 MCP 官方规范中的 MUST/SHOULD 与示例消息形状,给出可运维的参数表,不绑定某一 IDE 的实现细节。
问题背景:嵌套反向 RPC 与 Agent 主循环抢同一条会话
典型链路如下:编排运行时作为 MCP Client,向业务 MCP Server 发起 tools/call;Server 在处理工具逻辑时嵌套发出 sampling/createMessage 或 elicitation/create,Client 必须在该请求 ID 上返回 result 或 error,Server 才能继续完成工具并回传 tools/call 的最终结果。
这与「Agent 自己调 chat API」的区别在于:
- 凭据与模型选择归 Client:规范写明 sampling 使 Server 无需持有 API key;Client 根据
modelPreferences与hints选模型,并承担 token 费用。 - 规范强制人机回路(HITL)倾向:sampling 章节写明 SHOULD 始终有人类可拒绝;elicitation 要求 Server MUST NOT 通过 elicitation 索取敏感信息,Client SHOULD 展示请求方身份并允许用户 decline/cancel。
- 初始化能力声明不可省略:支持 sampling/elicitation 的 Client 在
initialize时 MUST 声明capabilities.sampling/capabilities.elicitation;未声明则不应期待 Server 发起对应请求(生命周期章节将二者列为 Client 侧可选能力)。 - 超时与取消是协议一等公民:lifecycle 章节要求实现为所有已发送请求建立超时;超时后发送方 SHOULD 发
notifications/cancelled并停止等待。反向 RPC 往往比tools/list更慢(等人、等 LLM),若与正向tools/call共用单一全局超时,极易误杀长工具。
因此,Agent 平台需要在 MCP 会话层之上增加反向 RPC 网关策略:审批、配额、模型映射、JSON Schema 校验与嵌套超时,而不是把 sampling/createMessage 当成普通内部 HTTP 回调。
可落地实现:能力开关、HITL、路由、限流与超时参数
1. initialize 与按租户的能力裁剪
网关代 Agent 连接 MCP Server 时,应区分「产品级能力」与「租户策略」:
| 策略项 | 建议默认 | 说明 |
|---|---|---|
capabilities.sampling |
生产默认 关闭,按 Server 白名单开启 | 每次采样可能产生 LLM 费用与数据出境 |
capabilities.elicitation |
对受信 Server 开启 | 无 elicitation 时,Server 只能把参数猜全或失败 |
| Server 目录登记 | 每个 MCP URL 记录 allows_sampling、allows_elicitation |
与 OAuth scope / 工具审批策略对齐 |
initialize 中勿声明客户端不具备的 capability;否则 Server 合法发起的反向请求会被静默丢弃或错误应答,导致工具半途中止。
2. sampling/createMessage:审批流与模型路由
规范推荐的交互是:Client 展示请求 → 用户可编辑 prompt → 调用 LLM → 用户可审查回复 → 再 result 回 Server。网关落地时可拆为四个可配置阶段,并记录审计事件 sampling.request_id、mcp_session_id、server_id。
模型选择(modelPreferences) 应实现为确定性映射表,而非把 hints[].name 直接当作 provider model id:
| 输入 | 网关行为 |
|---|---|
intelligencePriority / speedPriority / costPriority(0–1) |
在租户允许的模型清单中做加权排序;并列时优先较便宜且延迟可观测的型号 |
hints |
按顺序做子串匹配;无匹配时回退到租户 default_sampling_model |
maxTokens |
取 min(server.maxTokens, tenant.cap),防止 Server 拉高账单 |
用户拒绝时,规范示例使用 JSON-RPC error(message: User rejected sampling request)。网关 SHOULD 使用稳定、可分类的 message 前缀或应用定义错误数据,便于 Server 区分「用户拒绝」与「基础设施失败」,同时避免把内部堆栈写入 JSON-RPC error。
内容类型:sampling 消息可含 text、image、audio(base64 + mimeType)。网关在上传 LLM 前应做 MIME 白名单与大小上限(例如单段 image 解码后 ≤ 4 MiB,可按产品调整),并对 base64 解码失败返回 JSON-RPC error,而不是透传 provider 的 4xx 原文。
3. elicitation/create:Schema 子集与三态响应
elicitation/create 的 requestedSchema 仅支持扁平 object + primitive 属性(string/number/integer/boolean、enum、有限 format:email、uri、date、date-time)。网关在校验用户输入前应:
- 拒绝 Server 提交的嵌套 object、array of object 等(与规范「故意不支持」一致),直接 JSON-RPC error,避免 UI 生成器崩溃。
- 将
message与requestedSchema渲染为可访问表单;SHOULD 在 UI 标明 Server 名称与 MCP 连接来源(规范 Security Considerations)。 - 将用户操作映射为规范三态:
accept(带content)、decline、cancel;三者对 Server 语义不同,网关不得一律当成「空响应」。
推荐对 accept 的 content 做 JSON Schema 校验(与 Server 提供的 schema 一致),失败时留在 Client 侧重试,而不是把脏数据回传 Server。
4. 限流与并发槽位
规范对 Client 的 Security Considerations 均列出 SHOULD implement rate limiting。建议按「每个 MCP 会话」与「每个租户」两个维度设限:
| 计数器 | 建议初值(可按流量调参) | 触发动作 |
|---|---|---|
sampling/createMessage 每会话每分钟 |
6 | 返回 JSON-RPC error;可选 retryAfterMs 写入 error.data(非规范字段,需 Server 容错) |
elicitation/create 每会话每分钟 |
12 | 同上;elicitation 通常轻于 LLM,但应防 UI 轰炸 |
| 嵌套深度 | 1(工具内禁止再开 tools/call 期间的第二条 sampling) | 防止 Server 链式采样;深度 >1 时拒绝并记安全日志 |
与 LLM Provider 的并发槽位(in-flight chat 请求数)应共享:sampling 占用的槽位与 Agent 主循环的 chat 请求共用池,避免反向 RPC 挤满连接导致主任务饥饿。
5. 嵌套超时、progress 与 cancellation
lifecycle Timeouts 条款要点:
- 所有请求 SHOULD 有超时;超时后发送方 SHOULD 发
notifications/cancelled并停止等待。 - 收到与请求对应的 progress 通知时 MAY 重置超时钟,但 SHOULD 仍有绝对上限(maximum timeout),防止恶意 progress 无限续期。
网关推荐分层:
| 请求类型 | 软超时(可因 progress 续期) | 硬上限 |
|---|---|---|
tools/call(含嵌套反向 RPC) |
120s | 600s |
sampling/createMessage(不含用户思考时间) |
60s(仅 LLM 往返) | 180s |
| 用户 HITL 等待(审批 / 表单) | 单独计时 300s | 900s |
elicitation/create 用户填写 |
300s | 900s |
用户关闭审批 UI 时,Client SHOULD 对 in-flight 的 sampling/elicitation 发 notifications/cancelled(reason 含 user_dismissed),并忽略迟到 response(cancellation 章节 race 要求)。
正向 tools/call 超时后,除取消 Server 侧请求外,还应取消尚未完成的子请求(若同一 Server 在工具内仍占有着 sampling 的 id),防止 Server 继续产生 LLM 费用。
6. 与 Streamable HTTP / 多 Worker 的衔接
多 Pod 网关若无粘性会话,MCP Mcp-Session-Id 与 in-flight JSON-RPC id 可能落到不同实例,导致反向 RPC 无法配对。反向 RPC 网关要求:
- 同一会话的 JSON-RPC 状态机(pending id → 审批状态机)保存在会话粘性层或集中存储(Redis/DB),且
notifications/cancelled与响应必须由持有 pending 状态的实例处理。 - 用户 HITL UI 通过
requestId与 WebSocket/SSE 订阅绑定;换实例时从存储恢复 pending 元数据。
此节为实现约束,不属 MCP 规范 MUST,但缺少时生产环境常见「偶发永远等待」。
风险与边界
Server 借 sampling 外泄数据。 即使无 API key,Server 仍可通过构造 messages 让 Client 把上下文发往 LLM。应对:默认关闭 sampling;开启时强制审批 UI 展示完整 messages;记录出站 prompt 哈希与模型名供审计。
elicitation 钓鱼与敏感字段。 规范禁止 Server 用 elicitation 要敏感信息,但无法自动识别「密码」「API key」等字段名。Client 应对 requestedSchema.properties 做 denylist(如 /password/i、/secret/i),命中则自动 decline 并告警。
hints 与优先级被滥用。 modelPreferences 仅为 hint;恶意 Server 可设极高 intelligencePriority 诱导选最贵模型。租户侧应对映射结果做「最高价」封顶。
JSON-RPC error code 不统一。 sampling 示例使用 code: -1;网关扩展错误时应避免与 JSON-RPC 标准错误码冲突,并文档化 Server 需处理的 message 集合。
取消竞态导致重复计费。 cancellation 到达前 LLM 可能已完成;Client 无法撤回已发生的 provider 计费。应对:审批通过后再调 LLM;对「已取消但仍返回」的响应丢弃且不入库。
规范演进。 elicitation 在规范中标注为新版引入、设计可能变化;升级 MCP 协议版本时需重读 Client capabilities 与两章 Security Considerations,避免静态网关规则与新版 MUST 冲突。
参考来源
- Model Context Protocol — Sampling(2025-06-18) —
sampling/createMessage、modelPreferences、HITL SHOULD、错误示例 - Model Context Protocol — Elicitation(2025-06-18) —
elicitation/create、requestedSchema子集、accept/decline/cancel 三态 - Model Context Protocol — Lifecycle(2025-06-18) — Client
sampling/elicitation能力、超时与 progress 条款 - Model Context Protocol — Cancellation(2025-06-18) —
notifications/cancelled与竞态处理 - MCP 规范源码:
sampling.mdx— 消息与 Security Considerations 原文 - MCP 规范源码:
elicitation.mdx— Schema 限制与响应动作原文 - MCP 规范源码:
lifecycle.mdx— Timeouts 段落原文
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。