当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > MCP 多轮往返请求怎样让工具调用继续执行

MCP 多轮往返请求怎样让工具调用继续执行

来源:17golang原创 2026-11-04 12:50:03 0浏览 收藏

MCP 工具调用返回 input_required 时,不要重新发起一条新的工具请求,也不要把服务器返回的状态值改成自定义字符串。正确做法是保存本轮的输入请求和不透明 request_state,由客户端处理 sampling、elicitation 或 roots,再携带原状态重试原始调用,直到工具给出最终结果。

官方地址:https://modelcontextprotocol.io/

把这类调用看成“暂停—补充输入—继续”的同一条请求链:工具负责声明还缺什么,客户端负责提供答案,重试时原样带回状态。

先区分普通结果与 input_required

普通的 tools/call 可以直接返回内容;多轮往返请求则表示工具执行过程中需要客户端参与。现代 MCP 规范把这类结果包装成输入需求,客户端完成处理后再重试原始请求。它不是新的会话,也不是让客户端猜测服务器内部状态。

排查时先看三个字段:结果是否标记为需要输入、输入请求的类型是什么、服务器给出的状态值是否被原样保存。如果把 request_state 解析后再重新序列化或自行拼接,服务端可能无法识别下一轮请求。

MCP 多轮往返请求中工具、客户端与输入响应之间的静态关系说明图
图1:MCP 多轮往返请求的结构说明图,展示工具调用、客户端输入和原始状态的关系;这是静态说明图,不是运行截图。

保存 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,继续进入同一处理循环;如果返回工具结果,则结束。应用可以设置次数和总耗时上限,但上限触发后应记录当前请求键和错误类型,方便判断是能力不足还是工具本身反复索取输入。

MCP input_required 到 inputResponses 再到最终工具结果的状态关系说明图
图2:多轮往返请求的状态关系说明图,强调 requestState 的透传和 inputResponses 的键匹配;这是静态说明图,不是运行截图。

调用卡住时按四层清单排查

第一层看协议版本:客户端和服务器是否协商了同一代多轮往返机制。第二层看 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/

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Gemini API context caching怎样复用长文档上下文Gemini API context caching怎样复用长文档上下文
上一篇
Gemini API context caching怎样复用长文档上下文
vLLM prefix caching怎样减少重复提示词延迟
下一篇
vLLM prefix caching怎样减少重复提示词延迟
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    880次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    876次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    960次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    941次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    768次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码