先记住这个答案
客户端完成初始化并发现工具后,发送 tools/call 请求,在 params 中给出工具 name 和 arguments,用 id 关联响应。服务端检查报文、具体输入和授权,再执行工具。正常工具结果位于 result,包含 content,可额外提供 structuredContent;工具失败可以在 result 中设置 isError: true。按 2025-11-25,报文不符合 CallToolRequest、未知工具等问题走 JSON-RPC error,而具体工具的输入校验和业务失败走工具结果反馈。不能把所有参数错误都归为协议错误,也不能把 result 存在当作业务成功。
- 按请求 id 关联,按对应工具的契约解析结果
- 报文结构错误与具体工具参数失败分层处理
- 超时或返回失败并不自动证明没有副作用
一次调用需要先绑定实际工具契约
宿主发现 search_notes 后,应保留它当前的参数定义,并将用户查询组织成 arguments 对象。工具名称、输入结构和当前访问权限可能变化,不能把旧缓存里存在的工具当作服务端永远可以执行的动作。
例如 limit 超过工具允许上限,服务端可以在执行搜索前返回下面的工具失败结果,让 Agent 修正字段。请求 id 要与本次 tools/call 一致,content 给出可操作的信息,不应把服务器内部堆栈或其他用户数据一起暴露。
{
"jsonrpc": "2.0",
"id": "call-8",
"result": {
"content": [{ "type": "text", "text": "limit 必须是 1 到 10 之间的整数" }],
"isError": true
}
}假设外层请求结构有效且工具已找到,limit 违反的是具体搜索工具的参数要求。这个例子不表示所有 SDK 必须使用完全相同的错误文案,也不把外层成功响应称为搜索成功。
用违反的契约区分两个错误通道
如果 params 缺少工具名,接收端无法按 CallToolRequest 解释调用,这是报文层问题;如果 name 已识别,arguments 中的日期不合法或数量越界,则是工具输入校验问题。两者都可能发生在业务执行之前,所以时间先后不是分层依据。
客户端先解析 JSON-RPC error 或 result 分支,再对 result 检查 isError,并消费对应内容。未知工具通常需要重新发现或检查配置;具体字段不合法可以按反馈修正。权限拒绝则应回到允许范围,不能反复改参数尝试获得更高权限。
结果、超时和重试需要独立的状态判断
没有响应可能是请求尚未到达,也可能是动作已完成而返回丢失。对于发送通知、修改文件等副作用,客户端应查询执行状态或使用业务幂等键,不能因为超时就无条件重放。取消通知也不等于服务端已经撤销产生的结果。
structuredContent 是可选的结构化结果,并不因为没有 outputSchema 就按协议自动失效;若声明了输出 Schema,服务端需要满足它,客户端应验证。日志和界面应分开记录协议状态、工具状态与可确认的副作用,避免用单个 success 字段覆盖全部含义。
容易答错的地方
- 所有执行前的参数校验错误都用 -32602
- 具体工具的输入校验可以返回 isError 结果;协议错误针对调用报文等契约,不能单看错误发生在执行前还是执行后。
- 未捕获异常必须一律包装成普通工具失败
- 可预期工具失败应返回可理解的工具结果,但内部服务异常也可能走协议错误或中断连接;客户端不能依赖服务端一定给出同一种包装。
面试官还会怎么问?
content 和 structuredContent 都是必填吗?
普通 CallToolResult 中 content 必填,structuredContent 可选。是否有 outputSchema 影响结构约束,并不表示缺少它时必须丢弃所有结构化结果。
工具返回 isError 后可以自动重试吗?
需要看错误是否可修正或可恢复,还要核对副作用状态。参数越界可以修改,授权拒绝应停止越界动作,状态未知的写操作应先查询。
多个工具同时调用怎样避免结果串到一起?
按连接和请求 id 分别维护等待项,并对每个工具单独解析结果与错误。一个调用失败不代表其他调用也失败,但业务依赖关系仍需上层处理。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。