多轮往返请求
社区译文;来源: MCP 80041ab0
roots/list、sampling/createMessage 或 elicitation/create。此前由服务器发起请求的模式不再受支持。这是一项破坏性变更。_meta 请求元数据(io.modelcontextprotocol/protocolVersion、io.modelcontextprotocol/clientInfo 和 io.modelcontextprotocol/clientCapabilities)。每个请求都MUST包含必需的 _meta 字段;参见 _meta。多轮往返请求
模型上下文协议(MCP)定义了多种方式,让服务器在处理客户端请求期间向用户请求额外信息,例如 roots/list、sampling/createMessage 或 elicitation/create。多轮往返请求模式提供了一种标准方法来处理这些服务器请求,而不需要在服务器实例之间建立共享存储层,也不需要有状态负载均衡。
其高层流程如下:
- 客户端向服务器发送初始请求,并带上执行操作所需的参数。
- 服务器确定完成请求需要额外信息,并返回响应请求更多信息。
- 客户端从用户或其他来源收集所需信息,然后在原请求中加入额外信息并重试。
- 服务器确定已有足够信息完成操作,并返回最终结果。
sequenceDiagram
participant C as Client
participant S as Server
C->>S: 客户端请求(id: 1,请求参数)
note over S: 服务器需要更多信息<br/>才能处理请求。
S-->>C: 请求额外输入。
note over C: 客户端收集输入并<br/>重试初始请求。
C->>S: 客户端请求(id: 2,请求参数、所需输入)
note over S: 服务器已有足够信息<br/>完成请求。
S-->>C: 结果(id: 2,result)
核心类型
MCP 使用以下类型实现该流程。
InputRequests
InputRequests(英文)对象是服务器到客户端请求的映射。键是由服务器分配的字符串标识符;值是请求对象,例如 ElicitRequest(英文)、CreateMessageRequest(英文) 或 ListRootsRequest(英文)。
{
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
},
"capital_of_france": {
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
}InputResponses
InputResponses(英文)对象是客户端对服务器请求所作响应的映射。键对应 InputRequests 映射中的键;值是客户端对各请求的结果,例如 ElicitResult(英文)、CreateMessageResult(英文) 或 ListRootsResult(英文)。
{
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
},
"capital_of_france": {
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
}InputRequiredResult
InputRequiredResult(英文)是 Result 的一种类型,表示还需要额外输入才能完成请求。
inputRequests(可选):服务器发起且客户端必须完成的请求所组成的InputRequests(英文) 映射。requestState(可选):仅对服务器有意义的不透明字符串。客户端MUST NOT检查、解析、修改其内容或对其内容作出任何假设。
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
// Elicitation request.
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
},
// Sampling request.
"capital_of_france": {
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"modelPreferences": {
"hints": [{ "name": "claude-3-sonnet" }],
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
},
"requestState": "AEAD-protected blob"
}
}支持的请求
服务器MAY在以下客户端请求中返回 InputRequiredResult 响应:
| 客户端请求 | 支持 InputRequiredResult |
|---|---|
prompts/get(英文) | 是 |
resources/read(英文) | 是 |
tools/call(英文) | 是 |
服务器MUST NOT在其他任何客户端请求中发送 InputRequiredResult 响应。
基本工作流
基本工作流说明服务器如何在客户端-服务器请求中向客户端请求额外输入。此示例使用 tools/call 作为客户端请求,但同一模式适用于上面列出的所有受支持请求。
特别是,该模式允许服务器请求额外信息,而无需维护任何服务器端状态。服务器将所需上下文编码到 requestState 字段中,客户端重试时原样回传该字段。
sequenceDiagram
participant U as User
participant C as Client
participant S as Server
C->>S: tools/call(id: 1)
note over S: 服务器通过信息征询需要更多信息
S-->>C: InputRequiredResult(id: 1、ElicitRequest、requestState)
note over C,S: 初始请求终止
C->>U: 提示用户输入
U-->>C: 提供响应
note over C: 客户端携带 inputResponses 和 requestState<br/>重试工具调用
C->>S: tools/call(id: 2、ElicitResult、requestState)
note over S: 服务器重建状态<br/>完成执行
S-->>C: 结果(id: 2、ToolCallResult)
请注意,各步骤中的请求完全相互独立:处理重试请求的服务器不需要该重试请求中直接提供的信息之外的任何信息。
服务器要求(基本工作流)
服务器MAY使用
InputRequiredResult响应任何受支持的客户端请求。InputRequiredResultMAY包含inputRequests字段。inputRequests的键是服务器分配的标识符,在请求作用域内MUST唯一。inputRequests的值是请求对象,并且MUST是ElicitRequest(英文)、CreateMessageRequest(英文) 或ListRootsRequest(英文) 之一。
InputRequiredResultMAY包含requestState字段。如果指定,该字段是仅对服务器有意义的不透明字符串。服务器可自由使用任意格式编码状态,例如 base64 编码的 JSON、加密 JWT 或序列化二进制数据。如果客户端请求包含
requestState字段,服务器MUST将requestState视为攻击者可控的输入。如果requestState会影响授权、资源访问或业务逻辑,服务器MUST保护其完整性(例如使用 HMAC 或 AEAD),并且MUST拒绝未通过验证的状态。只有在篡改最坏也只会导致请求失败时,才MAY省略完整性保护。为防止重放,服务器SHOULD在受完整性保护的
requestState有效载荷中包含并在收到时验证以下内容:- 已认证主体;如果由不同主体提交状态,则拒绝。
- 较短的过期时间(TTL);状态过期后提交则拒绝;
- 原始请求的标识符,例如方法名及其关键参数的摘要;如果提交状态的请求不匹配,则拒绝。
请注意,这些措施限制了重放窗口并防止跨用户和跨请求复用,但本身不能保证只能使用一次。如果服务器要求某个 `requestState` 最多只能消费一次(例如一次性兑换),则服务器**MUST**在服务器端强制执行该不变量。
- 每个
InputRequiredResult响应中,服务器MUST至少包含inputRequests或requestState之一。 - 如果客户端没有在其能力中声明支持某类请求,服务器MUST NOT发送包含该类请求的
inputRequests。例如,如果客户端没有声明支持elicitation,服务器就MUST NOT在inputRequests字段中包含任何elicitation/create请求。 - 服务器MUST NOT假设客户端会完成
inputRequests或重试原始请求。如果服务器希望反复提示用户提供信息,直到获得完成请求所需的内容,服务器MAY在同一请求的多次尝试中重复返回InputRequiredResult。
客户端要求(基本工作流)
- 如果客户端收到包含
inputRequests字段的InputRequiredResult,则在重试原始请求前,客户端MUST构造所请求的输入。如果InputRequiredResult_不_包含inputRequests字段,客户端MAY立即重试原始请求。 - 如果
InputRequiredResult包含requestState字段,客户端在重试原始请求时MUST原样回传该字段。客户端MUST NOT检查、解析、修改requestState的内容或对其内容作出任何假设。如果InputRequiredResult不包含requestState字段,客户端在重试时MUST NOT包含该字段。 - 初始请求与重试请求相互独立,因此二者的 JSON-RPC
idMUST不同。 inputRequests和requestState字段都只影响客户端对原始请求的重试。它们MUST NOT用于客户端可能并行发送的任何其他请求。
错误处理
服务器SHOULD验证客户端提供的数据是否为有效的 InputResponses 对象,以及其中的信息能否正确解析。协议错误(格式错误的 JSON、无效 schema、内部服务器错误)SHOULD返回带适当错误码和消息的 JSON-RPC 错误响应。
如果 InputResponses 对象中包含额外的意外参数,服务器SHOULD忽略无法识别或不需要的信息。
如果客户端未发送先前 InputRequests 中请求的全部信息,而缺失的信息是服务器处理请求所必需的,服务器SHOULD返回新的 InputRequiredResult,再次请求缺失的信息,而不是返回错误。
安全注意事项
由于 requestState 会经过客户端,恶意或已被攻破的客户端可能尝试修改该字段,以改变服务器行为、绕过授权检查或破坏服务器逻辑。服务器MUST按照上文服务器要求验证请求状态。