MCP 报错分不清?先看响应里是 result 还是 error

MCP 里的失败分两条完全不同的通道。第一条是工具执行失败 :它是一次成功的 JSON-RPC 响应,result 里带 isError: true。消息内容会被模型读到,模型有机会自己修正后重试S2。第二条是协议错误 :它是一次 JSON-RPC error 响应,顶层是 error: {code, message}。这类响应由宿主程序的 MCP 客户端代码处理,模型根本看不到它S2。

区分方法只需要一步:看原始响应的顶层字段是 result 还是 error。适用情形是你在调试 tools/call 的报错、决定错误信息写给谁看,或者在排查"模型为什么没反应"。不适用于本地网络层故障------那不是任何一方的协议消息,后面会说边界。

假设一个具体的排查场景

假设(例如)你接了一个天气查询的 MCP server,某天模型连续三次调用 get_weather 都没成功,用户界面只显示一个笼统的"调用失败"。你打开日志,看到两种长得有点像的东西混在一起:有的响应是 {result: {content: [...], isError: true}},有的是 {error: {code: -32602, message: "..."}}。如果你把两者都当成同一种"工具报错"处理------比如都往用户界面上抛、或者都塞回给模型------其中一半信息会被送错对象。

为什么协议要分两条通道

MCP 的底层是 JSON-RPC 2.0,一次请求的响应只有两种形态:带 result 的成功响应,或带 error 的错误响应S4。工具执行失败被放在前一种里------JSON-RPC 层面"成功"地返回了一个结果,只是这个结果声明自己 isError: true。TypeScript SDK 的错误文档把区别讲得很直白:工具错误是"模型读到并从中恢复"的东西,协议错误是"模型永远看不到"的东西S2。

判断标准不是"错误严不严重",而是谁能修正它 。tools/call 由模型驱动,模型可以改参数重试------记录不存在、日期格式错、上游限流,这些放进 isError: true,且文字里要写清楚怎么改,因为那是模型唯一能拿到的东西S2。而 resources/read、prompts/get 这类请求由宿主应用驱动,失败就是抛给调用方代码的协议错误S2。所以同一个"参数格式不对",在工具调用里通常该是工具错误,在资源读取里就是 -32602。

第三方框架的实践也印证这条分界:请求结构问题(未知工具名、schema 校验失败、无效游标)走协议错误;模型能自纠的输入校验失败、业务逻辑错误、超时、限流走 isError: trueS3。

可复现的检查步骤

前提:能拿到一条 tools/call 的原始 JSON-RPC 报文。stdio 传输可以加一层日志代理打印双向消息;Streamable HTTP 可以在客户端记录响应体。关键是看线上原始字节,不要只看 SDK 抛出的异常------原因见下节。

检查本身三步:

  1. 从日志里取出那次失败的 tools/call 响应原文。
  2. 看顶层有 error 字段还是 result 字段。
  3. 如果是 result,再看里面的 isError 值。

对照一个决策表:

响应形态 归属 该给谁看
result + isError: true 工具执行失败 模型(写明修正线索)S2
result + isError: false 不是失败,是正常返回 检查是不是内容本身被误读了
顶层 error + code 协议错误 宿主客户端代码处理S2

三个探针请求(示例,未在本文环境实测)分别命中三种形态。对一个存在的工具传错误日期格式,预期得到 result + isError: true,message 里应有可操作的提示S3;对不存在的工具名发 tools/call,预期得到顶层 error,常见 code 是 -32602S3;用 SDK 的 in-memory client 调用,TS SDK 会把后者表现为 reject 出来的 ProtocolError,携带 {code: -32602, message: ...} 线上字段S2。

失败边界:这个检查在哪里会失灵

SDK 会抹掉信封差异。 在 TypeScript SDK 里,JSON-RPC error 响应会让 client.callTool 直接 reject 一个 ProtocolError,而 isError: true 是正常 resolve 的返回值S2。如果只在 catch 块里打日志,你看到的都是异常,分不出哪条是协议错误、哪条被框架包装过。所以检查必须落在原始报文层。

服务端抛异常不等于协议错误。 TS SDK 会把工具 handler 抛出的任何异常(包括抛出的 ProtocolError)转成 isError: true 的结果,异常 message 变成 content 文本S2。也就是说,"工具 handler 里 throw"和"显式返回 isError"对客户端是同一形态。唯一例外是 UrlElicitationRequiredError,它会作为 JSON-RPC error 透传给宿主S2。

本地超时不是任何一方的协议错误。 规范明确说,纯粹本地的错误(比如 SDK 内部产生的请求超时)目前没有被分配错误码,实现者在 JSON-RPC 形状的结构里暴露本地错误时,要确保它不会被误认成对端发来的错误S4。如果你把客户端超时异常打印出来发现也有 code 和 message,先确认它是本地造的还是服务端回的------看原始报文里有没有对应的响应。

旧版本 code 会撞车。 -32002(资源不存在)是 2025-11-25 及更早版本的 code,本版本实现不得再发出它,但客户端仍应接受旧 server 发来的这个 codeS4。排查时看到 -32002 不要直接当"未知错误",先查对端的协议版本。

另外注意错误码的区间划分:-32000 到 -32019 是各实现的历史遗留,新代码不得在此分配,接收方除 -32002 外不应假设其含义;-32020 到 -32099 保留给规范本身(目前有 -32020、-32021、-32022)S4。如果你的日志里出现这个区间的陌生 code,它不该来自一个合规的对端。

一条可以带走的检查动作

下次遇到"MCP 工具报错",先别改代码,把那条失败的 tools/call 响应原文找出来,回答两个问题:顶层是 result 还是 error?如果是 result,isError 是不是 true?第一个答案决定错误给模型还是给宿主代码,第二个答案防止你把正常返回误判成失败。跑完之后,欢迎把你的传输方式(stdio / Streamable HTTP)、SDK 和协议版本贴出来对照------上面 TS SDK 的行为是文档结论,你环境里的实际形态值得单独验证。

参考资料

相关推荐
原则猫10 小时前
UDP 到底怎么实现可靠的
前端
Java后端的Ai之路10 小时前
14、ReactJS - 路由(Router)
前端·react.js·状态模式
裕晟资质规划12 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
ShineWinsu12 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
月夏12 小时前
Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)
前端
广州华水科技12 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
梦曦i13 小时前
uni-storage 0.1.0 发布:TTL过期管理+多实例隔离
前端·storage·uni-app x
猫头_13 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
console.log('npc')14 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型
定栓14 小时前
Webpack迁到Vite第七节(二):全家桶移除 — Babel 全家桶深度分析
前端·webpack·node.js