跳到主要内容

04 · MRTR 与客户端能力

这是全协议最巧妙的一章。问题:无状态协议里,服务器处理一个工具调用时,发现“我得先问问用户的 GitHub 用户名 / 先让 LLM 生成一段文本 / 先要客户端的工作目录”——它怎么反过来向客户端要东西,又不留任何服务器端状态?答案是 MRTR(Multi Round-Trip Requests,多轮往返请求)

4.1 旧做法 vs 新做法

旧版(2025-11-25 及更早):服务器直接朝客户端发一个 JSON-RPC 请求(如 sampling/createMessage)。这要求传输层支持“服务器→客户端的独立请求”,且服务器得记住“我正等着这个回复”——这就是状态。

draft 版:这种服务器发起的请求被彻底废除,全部改走 MRTR(docs/specification/draft/basic/patterns/mrtr.mdx:7,这是 breaking change)。

4.2 MRTR 的核心流程

怎么读这张图:服务器不主动“推”请求,而是把“我需要什么”塞进结果里返回,客户端补齐后重发原请求

Client Server
│ 请求 (id:1,比如 tools/call) │
├────────────────────────────────────────►│ 发现:还差点信息
│ │ 把缺的信息打包进结果
│◄─────────────────────────────────────────┤ InputRequiredResult
│ resultType="input_required" │ { inputRequests, requestState }

│ ── 客户端去收集信息(问用户 / 调 LLM)──

│ 重发原请求 (id:2,新 id!) │
│ 带 inputResponses + 原样回显 requestState
├────────────────────────────────────────►│ 用 requestState 重建上下文
│◄─────────────────────────────────────────┤ Result(最终结果)

三个核心类型(docs/specification/draft/basic/patterns/mrtr.mdx:46):

  • inputRequests — 一个 map:服务器分配的 key → 它想让客户端替它发的请求(elicitation/create / sampling/createMessage / roots/list 三种之一)。
  • inputResponses — 客户端重发时带的 map:同样的 key → 对应的结果。
  • requestState — 一个对服务器才有意义的不透明字符串,客户端不得窥探/解析/修改,只能原样回显(docs/specification/draft/basic/patterns/mrtr.mdx:115)。

4.3 妙在哪:requestState 是“把状态外包给客户端”

这是整份规范最值得带走的设计。服务器不存任何会话状态——它把“处理到一半时需要记住的上下文”编码进 requestState,丢给客户端保管,重试时再拿回来重建(docs/specification/draft/basic/patterns/mrtr.mdx:190)。

好处:任意一台服务器实例都能处理重试,因为重试请求里已经自带了全部上下文,不需要共享存储、不需要粘性负载均衡(docs/specification/draft/basic/patterns/mrtr.mdx:18)。这正是“无状态”原则的极致体现。

但“把状态交给客户端保管”意味着客户端可能篡改它。所以规范给了严格的安全要求(docs/specification/draft/basic/patterns/mrtr.mdx:222):

  • 服务器必须requestState攻击者可控输入
  • 若它影响授权/资源访问/业务逻辑,服务器必须做完整性保护(HMAC 或 AEAD),校验失败就拒。
  • 防重放在受保护载荷里塞:认证主体(拒绝换人重放)、短 TTL(拒绝过期)、原请求标识(拒绝张冠李戴)。

几条对称的客户端/服务器义务也要记(docs/specification/draft/basic/patterns/mrtr.mdx:240):重试的 JSON-RPC id 必须和原请求不同(它们是独立请求);服务器不得发客户端没声明能力的 inputRequestsinputRequests/requestState 作用于这一个原请求的重试,不得用于并行的其它请求。

哪些请求能触发 InputRequiredResult?只有三种(docs/specification/draft/basic/patterns/mrtr.mdx:175):prompts/getresources/readtools/call。其它一律不行。

4.4 三个客户端能力都走这条管道

服务器通过 MRTR 能向客户端要的三样东西,正是客户端能力(client features)。它们现在都是 inputRequests 里的一项:

客户端能力方法服务器想要什么draft 状态
Elicitation(征询)elicitation/create向用户要结构化信息或引导其打开 URL活跃
Sampling(采样)sampling/createMessage借客户端的 LLM 生成一段内容已废弃
Roots(根目录)roots/list问客户端认为相关的目录/文件已废弃

draft 把 Sampling 和 Roots 标记为已废弃docs/specification/draft/client/sampling.mdx:7docs/specification/draft/client/roots.mdx:7,SEP-2577)。它们在弃用窗口内仍可用,但新实现不应采用——Sampling 改为直接对接 LLM 厂商 API,Roots 改为用工具参数/资源 URI/服务器配置传目录。下面仍简述,因为存量实现和旧版本里大量在用。

Elicitation(征询用户输入)

这是三者里唯一活跃的,也最有意思。它有两种模式docs/specification/draft/client/elicitation.mdx:12):

  • Form 模式 — 服务器给一份受限的 JSON Schema(只允许扁平对象 + 原始类型/枚举),客户端据此生成表单让用户填(docs/specification/draft/client/elicitation.mdx:114)。数据会经过客户端。
  • URL 模式 — 服务器给个 URL,引导用户去站外完成敏感交互(填密钥、付款、OAuth)。除 URL 本身外,数据不经过客户端docs/specification/draft/client/elicitation.mdx:322)。

这里有条铁律(docs/specification/draft/client/elicitation.mdx:30):服务器不得用 form 模式索要密码/密钥/令牌/支付凭证等敏感信息——这类必须走 URL 模式。理由很直接:form 数据会暴露给客户端和 LLM 上下文,敏感信息不该出现在那。

响应是三态模型(docs/specification/draft/client/elicitation.mdx:434):accept(同意并提交数据)、decline(明确拒绝)、cancel(直接关掉没选)。

URL 模式还防一类钓鱼攻击:Alice 触发一个 OAuth 授权 URL,却把它骗给受害者 Bob 点击,导致第三方令牌被绑到 Alice 名下(账号接管)。规范要求服务器必须确保“发起征询的用户”和“完成授权流的用户”是同一人(docs/specification/draft/client/elicitation.mdx:641)。

Sampling(借客户端的模型)— 已废弃

让服务器在处理请求时嵌套地调用客户端的 LLM 生成内容——好处是服务器不需要自己的 API key(docs/specification/draft/client/sampling.mdx:19)。它有一套模型偏好抽象:服务器不直接点名模型,而是给 costPriority/speedPriority/intelligencePriority 三个 0~1 优先级 + 可选 hints(模型名子串),客户端据此在自己可用的模型里挑(docs/specification/draft/client/sampling.mdx:560)。这是为了跨厂商:服务器要 Claude,客户端只有 Gemini 也能按相近能力映射。还支持采样中带工具循环(tools + toolChoice)。

Roots(暴露工作目录)— 已废弃

客户端把它认为相关的文件系统“根”(必须是 file:// URI)告诉服务器,是信息性指引而非访问控制——协议不强制服务器待在根内(docs/specification/draft/client/roots.mdx:19)。

代码地图

主题文件路径符号 / 锚点
MRTR 模式与 requestState 安全docs/specification/draft/basic/patterns/mrtr.mdxInputRequiredResult、Security Considerations
MRTR 核心类型schema/draft/schema.tsInputRequiredResultInputRequestsInputResponses
征询(两模式 + 钓鱼防护)docs/specification/draft/client/elicitation.mdxElicitRequest、URL Mode
征询类型schema/draft/schema.tsElicitRequestFormParamsElicitRequestURLParams
采样(已废弃)docs/specification/draft/client/sampling.mdxCreateMessageRequest、Model Preferences
根目录(已废弃)docs/specification/draft/client/roots.mdxListRootsRequest