MCP 多轮往返请求怎样让工具调用继续执行
来源:17golang原创
时间:2026-11-04 12:50:03 458浏览 收藏
MCP 工具调用返回 input_required 时,不要重新发起一条新的工具请求,也不要把服务器返回的状态值改成自定义字符串。正确做法是保存本轮的输入请求和不透明 request_state,由客户端处理 sampling、elicitation 或 roots,再携带原状态重试原始调用,直到工具给出最终结果。
官方地址:https://modelcontextprotocol.io/
把这类调用看成“暂停—补充输入—继续”的同一条请求链:工具负责声明还缺什么,客户端负责提供答案,重试时原样带回状态。
先区分普通结果与 input_required
普通的 tools/call 可以直接返回内容;多轮往返请求则表示工具执行过程中需要客户端参与。现代 MCP 规范把这类结果包装成输入需求,客户端完成处理后再重试原始请求。它不是新的会话,也不是让客户端猜测服务器内部状态。
排查时先看三个字段:结果是否标记为需要输入、输入请求的类型是什么、服务器给出的状态值是否被原样保存。如果把 request_state 解析后再重新序列化或自行拼接,服务端可能无法识别下一轮请求。

保存 request state 与输入请求
客户端收到工具结果后,先把它当作协议对象读取,不要只保留一段文本。下面用 Go 写一个简化的处理骨架,重点是把服务端返回的状态当作不透明字节保存,并让后续重试继续使用原始调用标识。
type InputRequired struct {
// Requests 描述客户端还需要处理的请求,键名由服务端定义。
Requests map[string]json.RawMessage `json:"inputRequests"`
// RequestState 只保存和回传,不在业务层解释或改写它。
RequestState json.RawMessage `json:"requestState"`
}
func savePending(result []byte) (InputRequired, error) {
var pending InputRequired
// 先解析协议外壳,解析失败要停止重试,避免发送不完整状态。
if err := json.Unmarshal(result, &pending); err != nil {
return InputRequired{}, fmt.Errorf("解析 input_required 失败: %w", err)
}
if len(pending.RequestState) == 0 {
return InputRequired{}, errors.New("缺少 requestState,不能安全重试")
}
return pending, nil
}
真实 SDK 会把这些字段封装成更具体的类型,但判断原则不变:状态只负责回到同一条请求链,输入请求才交给相应处理器。若响应中没有状态,宁可记录明确错误,也不要用空对象“补齐”。
按 capability 处理三类输入
输入请求可能要求客户端调用 sampling,让客户端模型生成一段结果;也可能要求 elicitation,让用户确认或填写字段;roots 则用于取得客户端声明的工作区范围。客户端必须先声明对应能力,并对拒绝、超时和能力缺失分别处理。
func answerRequest(ctx context.Context, kind string, body json.RawMessage) (json.RawMessage, error) {
switch kind {
case "sampling":
// sampling 由客户端的模型处理,应用仍应保留人工审核或拒绝分支。
return samplingHandler(ctx, body)
case "elicitation":
// elicitation 的答案来自用户,取消不能伪装成空字符串。
return elicitationHandler(ctx, body)
case "roots":
// roots 只返回客户端已声明的根,不能把它当成服务器权限授予。
return rootsHandler(ctx, body)
default:
return nil, fmt.Errorf("不支持的输入请求类型: %s", kind)
}
}
这里的函数名是应用层适配器示意。关键是把每个答案放回服务端给出的请求键中,并保留错误原因。用户拒绝 elicitation、客户端不支持 sampling、或者 roots 不可用,都应该结束当前重试链并返回可诊断错误。
带回 inputResponses 重试原始请求
答案准备好后,构造下一次请求时至少要保留三件事:原始工具调用的上下文、服务端返回的 requestState、按照请求键组织的 inputResponses。不要为了“简化参数”只发送答案。
type ContinueRequest struct {
// RequestState 必须原样回传,客户端不负责生成替代值。
RequestState json.RawMessage `json:"requestState"`
// InputResponses 的键必须和 inputRequests 一一对应。
InputResponses map[string]json.RawMessage `json:"inputResponses"`
}
func continueTool(ctx context.Context, pending InputRequired) ([]byte, error) {
answers := make(map[string]json.RawMessage, len(pending.Requests))
for key, requestBody := range pending.Requests {
kind, err := requestKind(requestBody)
if err != nil {
return nil, err
}
answer, err := answerRequest(ctx, kind, requestBody)
if err != nil {
return nil, fmt.Errorf("请求 %s 未完成: %w", key, err)
}
answers[key] = answer
}
// 仍然调用同一条工具请求的 continuation 入口,而不是创建新任务。
return retryToolCall(ctx, ContinueRequest{
RequestState: pending.RequestState,
InputResponses: answers,
})
}
如果重试后仍然返回 input_required,继续进入同一处理循环;如果返回工具结果,则结束。应用可以设置次数和总耗时上限,但上限触发后应记录当前请求键和错误类型,方便判断是能力不足还是工具本身反复索取输入。

调用卡住时按四层清单排查
第一层看协议版本:客户端和服务器是否协商了同一代多轮往返机制。第二层看 capability:客户端是否声明了工具实际需要的 sampling、elicitation 或 roots。第三层看数据:请求键是否完整、状态是否原样保存、答案是否放在 inputResponses。第四层看重试:是否重试原始调用、是否在每轮保留上下文、是否把拒绝和超时误当成成功答案。
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 一直停在 input_required | 请求键和答案是否一一匹配 | 记录键名,逐个生成响应,不发送空占位 |
| 服务端提示状态无效 | requestState 是否被解析后改写 | 使用原始字节或 SDK 的不透明字段 |
| 调用立即失败 | 客户端 capability 与协议版本 | 补齐能力声明,或明确返回不支持 |
| 用户取消后仍继续执行 | elicitation 的拒绝分支 | 把取消作为当前链路的终止结果 |
常见问题
requestState 可以保存到数据库后再继续吗?
可以把它作为不透明数据持久化,但要同时保存请求上下文、过期策略和安全边界;恢复时仍应原样交给协议客户端,不要把它当成业务字段修改。
roots 返回的目录就是安全权限吗?
不是。roots 表示客户端提供的工作区范围,工具实际访问文件时仍应执行自己的权限、路径和资源控制。
为什么不直接重新调用工具并把答案放进参数?
因为多轮往返的状态由协议管理。重新创建调用会丢失服务端上下文,也可能造成重复副作用;应沿原始请求链继续。
参考地址:https://blog.modelcontextprotocol.io/posts/2026-07-28/
Go SDK 参考:https://go.sdk.modelcontextprotocol.io/client/
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
科技周边 · 人工智能 | 56分钟前 | 性能优化 · 人工智能 · 深度学习 · PyTorch CUDA SDPA scaled_dot_product_attention Flash Attention214 收藏
-
科技周边 · 人工智能 | 57分钟前 | 性能优化 · python · ONNX Runtime InferenceSession SessionOptions 图优化 Execution Provider281 收藏
-
414 收藏
-
157 收藏
-
234 收藏
-
135 收藏
-
230 收藏
-
344 收藏
-
312 收藏
-
394 收藏
-
科技周边 · 人工智能 | 2小时前 | python · 人工智能 · gemini · 结构化输出 · Pydantic JSON Schema Gemini API google-genai structured output136 收藏
-
486 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习