MCP把工具失败写入结构化错误结果的实现方法
MCP 工具调用失败时,最稳的处理方式不是把一段“失败了”的字符串当成正常返回,而是在工具结果中明确设置 isError: true,同时让 content 告诉模型发生了什么。只有未知工具、无效协议参数或服务端无法完成请求这类协议层问题,才应该走 JSON-RPC 的 error。这样客户端知道该重试工具,模型也不会把业务失败误认为成功数据。
官方规范:https://modelcontextprotocol.io/specification/2025-06-18/server/tools
- 可由模型修正的参数、外部 API 或业务规则失败,放进工具结果并设置
isError: true。 content面向模型和用户,structuredContent面向程序;失败时不要让客户端继续信任成功数据。- 未知工具和协议级无效请求属于 JSON-RPC 错误,不能用业务错误结果掩盖。
先按失败层级选择返回方式
我在接入工具时最容易踩的坑,是把所有异常都直接抛到 transport 层。这样做看起来省事,但库存不足、第三方限流、权限不够等情况,本来都可以由模型修正参数或换方案,却被客户端当成一次请求崩溃。MCP 的边界很清楚:工具已经被正确找到并开始执行,执行结果里用 isError: true;请求本身无法按协议处理,才返回 JSON-RPC error。
| 场景 | 返回形态 | 调用方动作 |
|---|---|---|
| 业务条件不满足 | result.isError=true | 读取提示,修正条件或换工具 |
| 外部 API 暂时失败 | 工具结果中的错误文本 | 按错误码决定重试,避免盲重试 |
| 未知工具、非法 JSON-RPC 参数 | 顶层 error | 修复协议请求或工具注册 |
用 content 和 structuredContent 组成错误结果
成功结果可以同时提供可读文本和结构化对象:前者便于模型理解,后者便于客户端渲染或记录。失败时要先看 isError,再决定是否读取结构化字段。错误消息应包含动作、可修正方向和稳定错误码,不要泄露 SQL、内部路径或第三方响应中的密钥。

type ToolResult = {
content: Array;
isError?: boolean;
structuredContent?: { code: string; retryable: boolean; message: string };
};
function failed(code: string, message: string, retryable: boolean): ToolResult {
// content 给模型可读的下一步,结构化字段给客户端稳定消费。
return {
content: [{ type: "text", text: `${code}: ${message}` }],
isError: true,
structuredContent: { code, retryable, message },
};
}
function succeeded(data: { id: string; status: string }): ToolResult {
// 成功分支保持相同的数据语义,避免调用方按文本猜字段。
return {
content: [{ type: "text", text: `任务 ${data.id} 当前状态为 ${data.status}` }],
structuredContent: data,
};
如果当前 SDK 或客户端对失败结果不承诺读取 structuredContent,可以把关键错误码和修正建议放在文本 content 中;不要依赖只有某个客户端才解析的扩展字段。成功结果使用 outputSchema 时,结构化对象还应符合该 schema。
在工具边界捕获可预期业务异常
工具实现应把“用户能修正”的异常和“开发者必须排查”的异常分开。下面的 TypeScript 示例只把已知业务错误转换成 MCP 工具错误;未知异常记录服务端日志,向调用方返回不泄露内部细节的通用信息。
server.registerTool("reserve-seat", { inputSchema }, async ({ seatId }) => {
try {
const seat = await seats.get(seatId);
if (!seat) {
// 不存在是可修正的业务条件,模型可以改用其他座位号。
return failed("SEAT_NOT_FOUND", "座位不存在,请重新选择座位号", false);
}
if (seat.status !== "available") {
// 已被占用同样是工具执行结果,不应伪装成协议崩溃。
return failed("SEAT_UNAVAILABLE", "座位已被占用,请选择其他座位", true);
}
await seats.reserve(seatId);
return succeeded({ id: seatId, status: "reserved" });
} catch (error) {
// 真实项目应记录 traceId 和完整异常,但不把内部堆栈交给模型。
logger.error({ error, seatId }, "reserve-seat failed");
return failed("DEPENDENCY_UNAVAILABLE", "座位服务暂时不可用,请稍后重试", true);
}
});
这里的 retryable 是业务约定,不是 MCP 协议字段,作用是让宿主决定是否自动重试。模型看到 SEAT_NOT_FOUND 时应改变参数,看到 DEPENDENCY_UNAVAILABLE 时才考虑延迟重试。若输入根本无法通过工具 schema 校验,或调用了未注册工具,就不要在业务函数里制造一个假的工具结果。

客户端先检查 isError 再读取数据
客户端不要只判断请求有没有抛异常。一次 tools/call 可能正常收到结果,但结果里的 isError 已经说明工具没有完成目标。建议把这一步封装成统一适配器,并把错误码、是否可重试和原始文本写入审计日志。
const result = await client.callTool({ name: "reserve-seat", arguments: { seatId } });
if (result.isError) {
// 失败结果仍是协议响应,先展示可行动信息,再决定是否重试。
const text = result.content
.filter((item) => item.type === "text")
.map((item) => item.text)
.join("\n");
return { ok: false, message: text, retryable: false };
}
// 只有确认成功后,才把 structuredContent 当作业务数据使用。
return { ok: true, data: result.structuredContent };
发布前至少覆盖三组用例:有效座位返回成功对象;已占用座位返回 isError=true 且保留业务错误码;未知工具或非法参数返回协议错误。检查日志时还要确认 traceId 能把一次工具调用、外部依赖和最终结果串起来。
常见问题
工具返回错误字符串但不设置 isError 可以吗?
不建议。客户端会把它当成成功文本,模型也可能继续使用错误内容;可修正失败应明确设置 isError: true。
业务错误应该直接抛 JSON-RPC error 吗?
只有请求无法按 MCP 协议处理时才适合。库存不足、外部限流和权限条件通常属于工具执行错误,应留在结果中。
失败结果还要返回 structuredContent 吗?
可以,但要先确认客户端约定;关键错误码和修正建议必须同时出现在 content,避免只依赖结构化扩展。
LibTV无限画布适合什么任务?输入、参数与输出说明
- 上一篇
- LibTV无限画布适合什么任务?输入、参数与输出说明
- 下一篇
- Go time.Ticker动态调整周期而不丢状态的实现方式
-
- 科技周边 · 人工智能 | 3天前 |
- MCP区分资源读取与工具调用的实现方法
- 359浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | 错误处理 · 参数校验 · AI工程 · 函数调用 业务错误 JSON Schema Tool calling strict 工具参数校验 模型错误
- Tool calling校验工具参数并区分模型与业务错误的实现方法
- 380浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | 结构化输出 · AI工程 · Pydantic JSON Schema Structured Outputs
- Structured Outputs让模型结果贴合 JSON Schema的实现方法
- 191浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | 人工智能 · 结构化输出 · 大模型结构化输出 模型输出JSON缺字段 JSON兜底解析 JSON Schema校验 AI接口异常处理
- 模型输出 JSON 缺字段时如何设计兜底解析
- 272浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | 人工智能 · 本地推理 KV Cache batch size
- 本地推理 KV cache 和 batch size 如何做取舍
- 251浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | API · 性能优化 · ai · AI 提示词缓存 Responses API Prompt Caching
- AI 提示词缓存如何按稳定前缀组织请求
- 357浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 |
- 分类评测集不均衡时如何比较 macro 与 micro 指标
- 473浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 |
- Agent 工具返回文件路径时如何限制工作区范围
- 381浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 |
- AI 流式响应中的 finish_reason 如何决定持久化时机
- 195浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 121次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 196次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 139次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 113次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 95次使用
-
- Go代码规范错误处理示例经验总结
- 2022-12-23 278浏览
-
- 分析Go错误处理优化go recover机制缺陷
- 2023-01-01 483浏览
-
- Go 错误处理实践总结示例
- 2023-01-07 291浏览
-
- Go程序员踩过的defer坑错误处理
- 2023-01-19 195浏览
-
- golang gorm错误处理事务以及日志用法示例
- 2023-02-16 412浏览

