登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

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 解析后再重新序列化或自行拼接,服务端可能无法识别下一轮请求。

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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>