当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > MCP把工具失败写入结构化错误结果的实现方法

MCP把工具失败写入结构化错误结果的实现方法

来源:17golang原创 2026-09-19 22:06:52 0浏览 收藏

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、内部路径或第三方响应中的密钥。

MCP tools call 从业务失败到 isError、content 和 structuredContent 的结果结构说明图
图1:MCP 工具结果的结构说明图,展示业务失败如何进入 isError 与 content,而不是冒充成功数据。
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 校验,或调用了未注册工具,就不要在业务函数里制造一个假的工具结果。

MCP 工具边界区分业务错误、依赖故障与 JSON-RPC 协议错误的分层结构说明图
图2:错误边界结构图,区分可恢复业务失败、依赖异常和应上升到协议层的请求错误。

客户端先检查 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,避免只依赖结构化扩展。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
LibTV无限画布适合什么任务?输入、参数与输出说明LibTV无限画布适合什么任务?输入、参数与输出说明
上一篇
LibTV无限画布适合什么任务?输入、参数与输出说明
Go time.Ticker动态调整周期而不丢状态的实现方式
下一篇
Go time.Ticker动态调整周期而不丢状态的实现方式
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    121次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    196次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    139次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    113次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    95次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码