MCP错误处理
5129 字约 17 分钟
AIAgentMCP错误处理
2026-07-24
MCP 错误处理定义了协议在各类异常场景下的响应方式。与传统 API 错误处理不同,MCP 的错误消费者往往是 LLM——错误信息的质量直接影响模型是否能正确恢复并继续完成任务。
一、基本定义
MCP 错误处理是指在 Model Context Protocol 通信过程中,对协议层、传输层和业务层异常进行识别、分类、传递和恢复的机制。
MCP 基于 JSON-RPC 2.0 构建,错误响应遵循 JSON-RPC 的错误对象格式:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": { "detail": "field 'path' is required" }
},
"id": "req-001"
}核心特征:
- 结构化:错误信息以 JSON 对象传递,而非纯文本
- 分层:协议错误与业务错误有明确边界
- 面向模型:错误消息的最终消费者是 LLM,需要模型能理解并采取行动
二、为什么错误处理在 MCP 中特别重要
传统 API 的错误消费者是人类开发者,他们可以阅读文档、查日志、调试代码。MCP 的错误消费者是模型,这带来了本质区别:
- 模型需要理解错误才能调整行为:如果错误消息含糊(如 "Internal Server Error"),模型无法判断是参数问题还是服务故障,只能盲目重试或放弃
- 错误的表达直接影响 Agent 决策质量:清晰的错误描述 + 恢复建议可以让模型自主修正参数并重试,而模糊的错误会导致 Agent 陷入死循环
- MCP 调用链路长:Host → Client → Server → 外部服务,任何环节出错都需要向上游传递有意义的信息
- 安全约束:不应把数据库异常、内部堆栈和凭证信息直接返回给模型
三、在 MCP 体系中的位置
MCP 错误处理贯穿整个协议栈:
| 层次 | 错误来源 | 典型场景 |
|---|---|---|
| 传输层 | stdio / HTTP | 连接断开、超时 |
| 协议层 | JSON-RPC | 解析失败、方法不存在 |
| 业务层 | Tool / Resource | 参数校验、权限拒绝 |
| 外部依赖 | 数据库、API | 服务不可用、限流 |
错误需要沿着调用链向上传递,每一层都可以补充上下文但不能暴露内部细节。
四、错误分类
4.1 协议错误(Protocol Errors)
JSON-RPC 层面的错误,表示请求本身不符合协议规范。这类错误在到达业务逻辑之前就会被拦截。
常见场景:
- JSON 格式损坏,无法解析
- 请求对象缺少
method字段 - 调用了不存在的方法
- 参数类型或结构不符合 JSON Schema
4.2 参数错误(Invalid Params)
请求格式正确,但参数值不合法。这是最常见的错误类型。
常见场景:
- 必填参数缺失
- 参数类型不匹配(期望 string,收到 number)
- 参数值超出合法范围
- 参数之间的约束关系不满足
4.3 权限错误(Permission Denied)
请求合法但调用者没有执行权限。与 MCP安全边界 密切相关。
常见场景:
- 未提供认证凭证
- Token 过期或无效
- 资源访问被 ACL 拒绝
- 操作超出 Server 声明的能力范围
4.4 业务错误(Business Logic Errors)
请求合法且有权限,但业务逻辑不允许执行。
常见场景:
- 尝试删除不存在的资源
- 状态冲突(如修改已锁定的记录)
- 业务规则校验失败(如余额不足)
4.5 外部依赖错误(External Service Failures)
Server 在处理请求时依赖的外部系统出现故障。
常见场景:
- 数据库连接池耗尽
- 第三方 API 返回 5xx
- 网络分区导致超时
- 依赖的 MCP Server 自身报错
五、JSON-RPC 标准错误码
MCP 继承 JSON-RPC 2.0 的预定义错误码:
| 错误码 | 含义 | 说明 |
|---|---|---|
-32700 | Parse error | JSON 解析失败,请求体不是合法 JSON |
-32600 | Invalid Request | JSON-RPC 对象结构不合法(如缺少 method) |
-32601 | Method not found | 调用的方法在 Server 上不存在 |
-32602 | Invalid params | 参数校验失败 |
-32603 | Internal error | Server 内部未分类错误 |
关键原则:
-32603是兜底错误码。如果 Server 发生了未预期的异常,应返回此码,但data字段中只包含对用户/模型有意义的信息,不暴露内部堆栈。
六、MCP 协议特定错误
MCP 规范在 JSON-RPC 标准错误码基础上,定义了协议级别的特定行为:
- 初始化失败:在 Host-Client-Server 架构 中,如果 Client 与 Server 的
initialize握手失败(如协议版本不兼容、capabilities 协商失败),Server 应返回明确的错误并终止连接 - 能力不匹配:Client 请求了 Server 未声明支持的能力(如调用 Tool 但 Server 未声明
toolscapability),Server 返回Method not found(-32601) - 采样请求被拒绝:当 Server 向 Client 发起 sampling 请求时,Client 可以拒绝,此时返回协议约定的拒绝响应
MCP 规范没有定义大量自定义错误码,而是依赖 JSON-RPC 标准码 + data 字段扩展。这种设计保持了协议的简洁性。
七、错误分类维度
除了按来源分类,还可以按以下维度对错误进行正交划分:
7.1 临时错误 vs 永久错误
| 类型 | 特征 | 示例 |
|---|---|---|
| 临时错误 | 相同请求在条件变化后可能成功 | 网络超时、服务限流 |
| 永久错误 | 条件不变则永远不会成功 | 参数格式错误、权限不足 |
7.2 可重试 vs 不可重试
- 可重试:网络超时、
503 Service Unavailable、限流(需配合退避策略) - 不可重试:参数错误、权限拒绝、资源不存在
- 需判断:部分写入成功——需要幂等性检查后决定
7.3 用户错误 vs 系统错误
- 用户错误:调用方可以自行修正(如修正参数)
- 系统错误:需要服务端修复(如内部配置错误)
7.4 客户端错误 vs 服务端错误
- 客户端错误:Host/Client 发起的请求本身有问题
- 服务端错误:Server 处理过程中发生异常
八、超时处理
MCP 通信中的超时分为几类:
- 连接超时:Client 与 Server 建立连接的等待时间
- 请求超时:从发送请求到收到响应的最大等待时间
- 初始化超时:
initialize握手的等待时间
超时处理原则:
- 每个请求都应设置超时,避免无限阻塞
- 超时后应发送取消通知(JSON-RPC Notification),释放 Server 端资源
- 超时信息应明确告知模型"请求因超时失败",而非泛化的"请求失败"
九、取消操作
MCP 支持通过 JSON-RPC Notification 取消正在进行的操作:
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "req-001",
"reason": "User requested cancellation"
}
}取消的注意点:
- 取消是"尽力而为"(best-effort),Server 不保证立即停止
- 已完成的请求不可取消
- Server 收到取消通知后应停止处理并清理资源,但不需要返回响应
- 对于不可中断的操作(如数据库事务),Server 可以忽略取消但应在完成后丢弃结果
十、限流(Rate Limiting)
MCP Server 应对突发流量进行限流保护:
- 限流触发时返回明确的错误码和
Retry-After信息 - 在
data字段中告知当前限额和剩余配额 - 模型收到限流错误后应降低调用频率或切换到备选方案
{
"error": {
"code": -32000,
"message": "Rate limit exceeded",
"data": {
"retryAfter": 30,
"limit": 100,
"remaining": 0,
"windowSeconds": 60
}
}
}十一、部分成功(Partial Success)
批量操作中,部分子请求可能成功、部分失败。MCP Tool 返回结果时应体现这一点:
{
"content": [
{
"type": "text",
"text": "处理完成:3/5 成功"
}
],
"isError": true,
"structuredData": {
"succeeded": ["item-1", "item-2", "item-3"],
"failed": [
{ "id": "item-4", "error": "Permission denied" },
{ "id": "item-5", "error": "Not found" }
]
}
}模型需要知道哪些成功了、哪些失败了、失败原因分别是什么,才能决定后续操作。
十二、批量操作错误
批量操作中的错误处理策略:
- 全部或无(All-or-Nothing):任何一个子请求失败则整体回滚
- 尽力执行(Best-Effort):尽可能多地成功,失败的单独报告
- 遇错停止(Fail-Fast):遇到第一个错误就停止后续执行
策略选择取决于业务语义。Tool 设计时应在描述中明确告知模型采用哪种策略。
十三、错误码设计原则
- 使用标准码:优先使用 JSON-RPC 预定义错误码
- 扩展用
data:自定义信息放在data字段,不滥用错误码 - 层次清晰:协议错误、参数错误、业务错误使用不同的码段
- 保持稳定:错误码一旦发布不应改变含义
- 可枚举:在文档中列出所有可能的错误码及其含义
推荐的自定义错误码段(-32000 ~ -32099 为 Server 保留):
| 码段 | 用途 |
|---|---|
-32000 ~ -32009 | 限流与配额 |
-32010 ~ -32019 | 权限与认证 |
-32020 ~ -32029 | 资源状态冲突 |
-32030 ~ -32039 | 外部依赖故障 |
十四、人类可读错误消息
错误消息的 message 字段应同时服务于人类和模型:
- 简洁明确:一句话说清发生了什么
- 包含上下文:指出是哪个参数、哪个资源出了问题
- 避免技术黑话:不用 "SIGSEG in module 0x3F" 这类信息
- 提供行动方向:如 "参数 'date' 格式应为 YYYY-MM-DD,当前值为 '2024/1/1'"
好的错误消息:
"Invalid params: field 'startDate' expects format 'YYYY-MM-DD', got '2024/1/1'"差的错误消息:
"Bad request"十五、模型可理解错误
这是 MCP 错误处理与传统 API 错误处理的核心区别。
15.1 错误消息如何影响模型决策
模型根据错误信息决定下一步行动:
| 错误消息 | 模型可能的行为 |
|---|---|
| "参数 date 格式错误,应为 YYYY-MM-DD" | 修正格式并重试 ✅ |
| "Internal Server Error" | 无法判断,可能放弃 ❌ |
| "数据库连接失败,建议 30s 后重试" | 等待后重试 ✅ |
| "请求被拒绝" | 无法判断原因 ❌ |
15.2 结构化错误信息
在 data 字段中提供结构化信息,帮助模型精确定位问题:
{
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"field": "startDate",
"expected": "YYYY-MM-DD",
"received": "2024/1/1",
"suggestion": "Use '2024-01-01' instead",
"retryable": false
}
}
}15.3 提供恢复建议
错误信息中直接包含 suggestion 或 recoveryAction 字段,模型可以据此直接采取行动,而不需要"猜"应该怎么修正。
十六、内部错误脱敏
核心原则:不应把数据库异常、内部堆栈和凭证信息直接返回给模型。
错误信息会通过模型传递到用户界面,甚至可能被记录到日志中。必须对内部信息进行脱敏:
不应暴露的内容
| 类别 | 示例 | 风险 |
|---|---|---|
| 堆栈信息 | at com.example.Service.process(Service.java:42) | 暴露代码结构和内部实现 |
| 数据库异常 | SQL syntax error near 'SELECT * FROM users' | 暴露数据库结构和查询逻辑 |
| 凭证信息 | Authentication failed for api_key=sk-xxx... | 凭证泄露 |
| 内部地址 | Connection refused: 10.0.1.5:5432 | 暴露内部网络拓扑 |
| 第三方密钥 | AWS Secret Key invalid | 密钥泄露 |
脱敏策略
- 在 Server 边界捕获所有异常:将内部异常转换为对外友好的错误消息
- 使用错误码映射:内部异常类型 → 对外错误码 + 通用消息
- 保留内部日志:完整错误信息写入服务端日志,附带 Correlation ID
- 对外只暴露必要信息:错误码、简要描述、恢复建议
// 内部异常
ConnectionRefusedException: 10.0.1.5:5432 - password authentication failed for user "admin"
// 对外错误
{ "code": -32030, "message": "Database service temporarily unavailable", "data": { "retryable": true, "retryAfter": 10 } }十七、关联 ID(Correlation ID)
用于在分布式调用链中追踪请求:
- 每个请求在 Host 层生成唯一的 Correlation ID
- ID 沿着 Host → Client → Server 传递
- Server 在日志中记录该 ID,便于排查问题
- 错误响应中可以包含 Correlation ID,方便用户向服务端报告问题
{
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"correlationId": "trace-abc-123",
"retryable": true,
"contactSupport": "Provide this ID when reporting the issue"
}
}
}十八、重试策略
18.1 指数退避(Exponential Backoff)
每次重试的等待时间翻倍:
第1次重试:等待 1s
第2次重试:等待 2s
第3次重试:等待 4s
第4次重试:等待 8s18.2 抖动(Jitter)
在退避时间上加入随机偏移,避免多个客户端同时重试造成"惊群效应":
实际等待 = baseDelay * 2^attempt + random(0, baseDelay)18.3 最大重试次数
设置上限,避免无限重试:
- 默认最大重试 3 次
- 对于幂等读操作可适当放宽到 5 次
- 超过最大重试次数后,返回最终错误给模型
18.4 幂等性检查
重试前必须确认操作是否幂等:
- 幂等操作(查询、设置值):可以安全重试
- 非幂等操作(创建资源、发送消息):需要检查是否已执行成功
- 条件幂等(带唯一键的写入):可通过唯一键检测重复
十九、熔断(Circuit Breaker)
当某个 MCP Server 持续失败时,Client 应启动熔断保护:
熔断状态说明:
- Closed(关闭):正常状态,所有请求通过。连续失败达到阈值后切换到 Open
- Open(开启):快速失败状态,所有请求立即返回降级响应,不实际调用 Server
- Half-Open(半开):定时释放一个探测请求,成功则恢复为 Closed,失败则继续 Open
二十、降级策略
当 MCP Server 不可用时,Host/Client 的降级选项:
- 返回缓存结果:使用上次成功的响应(需标记为缓存数据)
- 切换到备选 Server:如果有提供相同能力的备选 Server
- 缩小请求范围:将批量请求拆分为更小的请求
- 告知模型能力受限:明确告知模型当前哪些 Tool 不可用,让模型调整计划
- 排队等待:将请求放入队列,等 Server 恢复后处理
降级时的错误消息应明确告知模型当前状态:
"Tool 'search_database' is currently unavailable (circuit breaker open).
Cached result from 5 minutes ago is available.
Suggestion: use cached data or retry in 60 seconds."二十一、错误处理决策树
二十二、完整示例:一个 Tool 调用的错误处理链路
场景:模型调用 query_database Tool 查询用户数据。
第一步:Host 发送请求
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": { "sql": "SELECT * FROM users WHERE id = 123" }
},
"id": "req-042"
}第二步:Server 处理时数据库超时
Server 内部捕获 ConnectionTimeoutException,转换为对外错误:
{
"jsonrpc": "2.0",
"error": {
"code": -32030,
"message": "Database query timed out after 30s",
"data": {
"tool": "query_database",
"retryable": true,
"retryAfter": 5,
"correlationId": "trace-042-db-timeout",
"suggestion": "Retry the query, or simplify the SQL to reduce execution time"
}
},
"id": "req-042"
}第三步:Client 传递错误给 Host
Client 检查到 retryable: true,在传递给 Host 时补充上下文:
{
"error": {
"code": -32030,
"message": "Database query timed out after 30s",
"data": {
"tool": "query_database",
"retryable": true,
"retryAfter": 5,
"attempt": 1,
"maxAttempts": 3,
"suggestion": "Retry the query, or simplify the SQL to reduce execution time"
}
}
}第四步:Host 决策
Host 将错误信息传递给模型。模型看到 retryable: true 和 suggestion,决定等待 5 秒后重试,并在重试时简化查询条件。
注意:整个链路中,数据库连接地址、SQL 引擎版本、内部堆栈均未暴露。
二十三、设计原则
- 错误信息服务于模型决策:每条错误消息都应帮助模型判断下一步该做什么
- 分层处理,逐层补充:每一层添加自己的上下文,但不穿透暴露下层细节
- 标准优先,扩展为辅:使用 JSON-RPC 标准错误码,自定义信息放入
data - 可重试性显式声明:每个错误都应标明是否可重试
- 脱敏是底线:内部异常永远不能直接传递给模型或用户
- 降级优于失败:尽可能提供部分结果或缓存数据,而非完全失败
- 关联 ID 贯穿全链路:便于问题追踪和排查
二十四、常见误区
| 误区 | 正确做法 |
|---|---|
| 把所有异常信息直接返回 | 在 Server 边界做异常转换和脱敏 |
| 错误消息只写 "Error" | 提供具体原因和恢复建议 |
| 无限重试 | 设置最大重试次数 + 指数退避 |
| 忽略超时 | 所有请求都设置超时并处理超时场景 |
| 不区分临时错误和永久错误 | 用 retryable 字段显式标记 |
| 批量操作要么全成功要么全失败 | 支持部分成功,报告每个子项的状态 |
| 错误码随意定义 | 使用 JSON-RPC 标准码 + 保留码段 |
| 忽略模型的决策需求 | 错误消息中包含结构化的恢复建议 |
二十五、实践检查清单
Server 端
Client 端
Host 端
二十六、与其他概念的关系
- MCP基础:错误处理是 MCP 协议通信的基础保障
- MCP架构总览:三层架构中每层都可能产生和传递错误
- MCP Tool能力:Tool 调用是错误处理的主要场景
- MCP安全边界:权限错误和错误脱敏与安全边界密切相关
- MCP Server设计:Server 设计需要内置完整的错误处理机制
- MCP Client设计:Client 设计需要实现重试、熔断、降级等策略
二十七、适用边界
本文档覆盖的范围:
- MCP 协议层和传输层的错误处理
- Tool 调用场景的错误传递和恢复
- 面向模型的错误信息设计
不涉及的范围:
- 具体编程语言的异常处理实现
- LLM 自身的错误(如幻觉、推理错误)
- MCP Server 内部的业务错误处理逻辑(只关注协议层面的传递)
- 前端 UI 层的错误展示