跳转至

A2C-SMCP 错误处理规范

状态: 草案

本章节目前为草案状态,具体错误码与结构仍在演进中。

概述

A2C-SMCP 协议定义了统一的错误处理机制,确保 Agent、Server、Computer 之间能够正确传递和处理错误信息。

错误码定义

通用错误码

代码 名称 含义 典型触发场景
400 Bad Request 无效请求格式 数据结构校验失败、字段缺失或类型错误
401 Unauthorized 未授权 认证失败、Token 无效
403 Forbidden 权限违规 跨房间访问、Agent 独占冲突、未授权操作
404 Not Found 资源不存在 工具或 Computer 不存在、MCP 配置缺失
408 Timeout 请求超时 工具调用超过约定超时时间未返回
500 Internal Error 内部错误 Server 或 Computer 端逻辑异常

工具调用错误码

代码 名称 含义
4001 Tool Not Found tool_name 在指定 MCP Server 中不存在;mcp_server 本身缺失场景(用 4014
4002 Tool Disabled 工具被禁用
4003 Tool Execution Failed 工具执行失败
4004 Tool Timeout 工具执行超时
4005 Tool Requires Confirmation 工具需要二次确认
4006 Tool Authorization Required 工具需要 MCP 上游授权(如 OAuth 2.0),Computer 当前无有效凭证或尚未完成授权(见下方4006/4007 判定决策表
4007 Tool Authorization Failed MCP 上游授权流程失败、Token 已失效、刷新失败、或权限不足(见下方4006/4007 判定决策表

MCP Server 路由错误码

代码 名称 含义
4014 MCP Server Not Found 引用的 mcp_server 名字未注册(见 §MCP Server Not Found
4015 MCP Capability Not Supported MCP Server 已注册但未声明所需 capability(见 §MCP Capability Not Supported

SKILL 通道错误码

代码 名称 含义
4016 Invalid Skill Name client:get_skill 入参 name 格式非法(违反 SKILL name lexer 规则;见 §Invalid Skill Name
4017 Skill Resource Not Accessible client:get_skillrel_path 路径穿越 / 命中禁止文件 / 包内不存在(见 §Skill Resource Not Accessible

复用与不使用:SKILL name 格式合法但不存在(不存在 / 已卸载 / 已孤儿)复用 4014 MCP Server Not Found 语义;name 格式非法 → 4016name 有效但 rel_path 不可达 → 4017。SKILL 通道不使用 4015——未声明 resources capability 的 server 在物化阶段即被排除,不会上送 Agent。物化失败 / 孤儿 / 跨 source 冲突等内部事故不进协议错误码(Computer 日志即可,batch 接口对部分失败健壮)。二进制 / 过大文本经 blob_handleclient:get_blob铸造期的沙箱与上限属 4017拉取期的句柄/源/范围属 4018,二者不重叠。

通用二进制传输错误码

代码 名称 含义
4018 Blob Not Accessible client:get_blob 句柄无效 / 重施鉴权失败 / 源消失 / 范围越界(见 §Blob Not Accessible

边界4018 属传输拉取期。资源的鉴权与绝对上限由铸造句柄的生产者通道在铸造期决断(SKILL → 4017,不通过则不铸造句柄)。详见 通用二进制传输

连接与房间管理错误码

代码 名称 含义
4008 Protocol Version Mismatch HTTP 握手阶段,URL query 中的 a2c_version 与 Server 不兼容
4101 Room Full 房间已有 Agent
4102 Room Not Found 房间不存在
4103 Not In Room 未加入房间
4104 Cross Room Access 跨房间访问被拒绝

错误响应格式

A2C-SMCP 协议级错误(HTTP 握手层 + Socket.IO ack 层)统一采用扁平 ErrorPayload shape:标准字段 code / message 顶层平铺,code-specific 字段(如 mcp_server_name / capability)顶层并列,诊断信息封装在 details 子对象内。

无嵌套 envelope

协议不使用 {"error": {"code": ..., "message": ...}} 形式的嵌套包装。SDK 反序列化时直接读取顶层字段,禁止二次 unwrap。

作用域:本节定义所有 client:* 事件 ack 层与 HTTP 握手层的协议级错误响应 shape——任一 client:* 路由(包括 client:get_tools / client:get_desktop 等历史未列举专属错误码的路由)在 ack 通道上返回的协议级错误, MUST 采用本节定义的扁平 shape;SDK 路由层 MUST 对 ack payload 命中 ErrorPayload(即顶层含 code 且语义匹配)做原样透传,不得按路由开关此契约。适用:(a) server:join_office 等返回 (success, error_msg) 元组的事件,见 §事件级错误处理;(b) client:tool_call 工具失败,使用 MCP CallToolResult.isError=true,见 §错误传播

Flat ErrorPayload schema

class ErrorPayload(TypedDict, total=False):
    code: int                       # 必选:错误码
    message: str                    # 必选:人类可读描述
    details: NotRequired[dict]      # 可选:诊断容器,仅供日志 / 调试
    # 各错误码可附加 code-specific 顶层字段,详见下方总表

传输分层

错误码 承载方式
HTTP 握手层 4008 HTTP 400 响应 body(JSON)+ X-A2C-Error-Code header(冗余诊断)
Socket.IO ack 层 4014 / 4015 ack callback 第一参(dict)

两层使用同一种 flat shape,差别仅在传输通道。

各错误码标准字段总表

code / message 外的顶层 code-specific 字段,以及 details 内推荐 key:

code 顶层 code-specific 字段 details 内推荐 key
4008 server_version / client_version / min_supported / max_supported
4014 mcp_server_name
4015 mcp_server_name / capability
4016 name
4017 reason / rel_path / total_size
4018 reason

各错误码完整 payload 示例与触发时机详见对应章节(§4008 / §4014 / §4015)。

details 字段约束(协议级)

details诊断信息容器,承载日志 / 调试上下文。协议级约束:

  • Agent MUST NOTdetails 内容透传给最终用户——避免泄露内部实现细节
  • 协议级标准 key 见上表;新增 standard key 算 minor 升级
  • 业务自定义 key 可附加,不进入协议演进语义(不算 breaking)

details 内禁止包含的敏感信息

  • API 密钥或 Token
  • 内部 IP 地址或端口
  • 数据库连接信息
  • 用户密码或凭证
  • 堆栈跟踪(生产环境)

SDK 反序列化建议

code 分发到 code-specific TypedDict 解析,不要写 over-generic details: dict 把所有顶层字段折叠收集——会丢失类型信息与 IDE 推导能力。

:协议规范仅提供 Python reference impl。其他 SDK 由各自实现决定具体解析模式——A2C-SMCP 协议文档不堆砌多语言示例。

事件级错误处理

server:join_office 响应

# 成功
(True, None)

# 失败
(False, "Room already has an agent")

client:tool_call 响应

工具调用使用 MCP 的 CallToolResult 结构返回结果:

class CallToolResult:
    content: list[TextContent | ImageContent | EmbeddedResource]
    isError: bool  # 是否为错误结果
    meta: dict     # 结果级元数据(线上 key 为 meta,承载 a2c_* 扩展标记)

isError=True 时,content 中包含错误信息。isError=True 可由普通失败超时取消产生;三者通过结果级 meta 标记区分:

终态 isError 结果级 meta 标记 说明
普通失败 True 无 a2c 终态标记(授权失败另带 meta.error_code,见 §MCP 上游授权错误响应 工具自身执行失败
超时 True meta.a2c_timeout = true Agent / Computer 端超时;见 §超时处理
取消 True meta.a2c_cancelled = true(+ 可选 meta.a2c_cancel_reason notify:tool_call_cancel 中断;见 事件 §notify:tool_call_cancel

标记完整定义见 数据结构 §CallToolResult 结果级 A2C 标记

超时处理

Agent 端超时

Agent 在发起工具调用时指定 timeout

result = await agent.emit_tool_call(
    computer="my-computer",
    tool_name="slow_tool",
    params={},
    timeout=30  # 30 秒超时
)

超时后,Agent 应:

  1. 发送 server:tool_call_cancel 取消请求
  2. 返回超时错误给调用方
# 超时返回示例
CallToolResult(
    content=[TextContent(text="Tool call timeout")],
    isError=True,
    meta={"a2c_timeout": True}   # 结果级标记:标识超时(区别于取消的 a2c_cancelled)
)

命名空间收敛: 历史示例曾用未加命名空间的 meta={"timeout": True};现统一到 a2c_* 命名空间下的 a2c_timeout,与 a2c_cancelled 一致,均为结果级 meta 标记。详见 数据结构 §CallToolResult 结果级 A2C 标记

Server 端超时

Server 在转发请求时应设置合理的超时:

  • 使用 Agent 请求中的 timeout
  • 添加少量缓冲时间(如 5 秒)

Computer 端超时

Computer 应在工具执行超时时:

  1. 尝试中断工具执行
  2. 返回超时错误

取消语义(无 ack、无错误码)

server:tool_call_cancelfire-and-forget:Server 收到后仅向房间广播 notify:tool_call_cancel不回执(无 ack)

取消刻意不分配错误码、不投递扁平 ErrorPayload,理由与 §飞行中断连 同源:

  • 无 ack 通道即无错误码读者;取消是广播通知而非请求-响应,强行定义回执错误码无意义。
  • Agent 端发出 server:tool_call_cancel 后收到的 ack 为 None合规预期MUST NOT 据此判定"未实现 / 失败"。

取消的"结果"通过原 client:tool_call 的 ack 体现,而非取消事件本身:被中断时 Computer 对原调用返回 CallToolResult(isError=True, meta={"a2c_cancelled": True}),见 §client:tool_call 响应

错误码复用 / 不使用:取消链路不新增错误码、亦不复用任何既有错误码(4004 Tool Timeout 仅用于超时,用于取消)。req_id 命中不到在途调用时 Computer 静默忽略(不回错误码),见 事件 §notify:tool_call_cancel

飞行中断连(in-flight disconnect)

Agent 发出 client:* 事件后,在 Server 转发 / Computer 处理 / 响应回程任一阶段断连,Server MAY 静默丢弃此 in-flight 请求(不 ack、不投递扁平 ErrorPayload)。

理由(为什么不分配新错误码)

  • ack callback 归属于 originator socket——originator 断连后,ack 投递路径本身已经消失,"错误码"没有读者,强行定义无意义
  • 是否在线属于 Socket.IO 传输层关注点,A2C 应用层不应越界用错误码表达连接状态——保持职责分层
  • 与"Computer 已响应但 Agent 在响应送达前断连"在本质上是同一类事件(originator 不可达),协议不为该类事件分配专属错误码

Agent 实现要求

  • MUST NOT 依赖 ack 超时检测自身断连——使用 Socket.IO disconnect / connect_error 事件
  • client:get_* 类事件建议本地 ack 等待超时 ≥ 30s(覆盖 Server 路由 + Computer 处理冗余)
  • client:tool_call 沿用 payload 内 timeout + Server 处理冗余(不另设 ack 超时)

Server 实现要求

  • in-flight 路径上发现 originator session 已不可达(如 session is None / SID 已注销)时,MUST NOT crash
  • 推荐 raise 一个受控的 namespace 异常(如 SMCPNamespaceError),由框架级 handler 静默收编为"不 ack"——使用 assert(生产 -O 模式会被剥离)
  • MAY 写入审计日志;MUST NOT 向 Computer 或其它房间成员广播"originator gone"事件
  • 已转发给 Computer 的请求若回程时 originator 已断连,Server 直接丢弃响应,回滚 Computer 端副作用——副作用幂等性是 Computer 自身的设计契约

重试策略

建议的重试策略

错误类型 是否重试 策略
400 Bad Request 修复请求后重试
401 Unauthorized 重新认证后重试
403 Forbidden 不重试
404 Not Found 不重试
408 Timeout 可选 指数退避重试
500 Internal Error 可选 指数退避重试

指数退避示例

async def retry_with_backoff(func, max_retries=3):
    for i in range(max_retries):
        try:
            return await func()
        except TimeoutError:
            if i == max_retries - 1:
                raise
            wait_time = (2 ** i) + random.uniform(0, 1)
            await asyncio.sleep(wait_time)

错误传播

工具调用错误传播链

MCP Server → Computer → Server → Agent
     │           │          │        │
     └───────────┴──────────┴────────┘
                错误信息保留

每一层应:

  1. 记录错误日志
  2. 将错误信息向上传播
  3. 不丢失原始错误细节

日志记录建议

# 推荐的日志格式
logger.error(
    "Tool call failed",
    extra={
        "req_id": req_id,
        "tool_name": tool_name,
        "computer": computer,
        "error_code": error.code,
        "error_message": error.message
    }
)

协议版本不匹配(4008)

触发时机:客户端(Agent / Computer)通过 Socket.IO 连接 Server,HTTP 中间件层校验 URL query 中的 a2c_version 发现与 Server 不兼容。校验发生在 Socket.IO 处理之前,业务代码无法影响。

4008 是 HTTP body code,不是 WS close code

明确语义边界

4008 是 ErrorPayload.code 字段值,承载于 HTTP 400 响应 body 中。 4008 不是 WebSocket close code。polling 握手路径上版本校验在 HTTP 层完成、发生在 WS 帧建立之前。 WS-only 直连握手另有拒绝形态:支持 ASGI denial-response 的栈仍回字节一致的 4008 HTTP body;不支持时回退到协议指定的、与 4008 取不同值的 WebSocket close code 4900(详见 versioning.md §5)。 SDK 实现 MUST NOT4008(ErrorPayload.code)与 4900(WS close code)混淆或互换。

HTTP 响应规范

HTTP/1.1 400 Bad Request
Content-Type: application/json
X-A2C-Error-Code: 4008

{
  "code": 4008,
  "message": "Protocol version mismatch",
  "server_version": "0.2.0",
  "client_version": "0.1.5",
  "min_supported": "0.2.0",
  "max_supported": "0.2.999"
}

X-A2C-Error-Code 响应 header 是冗余诊断辅助:当客户端无法访问 body 时(罕见 transport 边角情况),可从 header 中识别错误类型。不替代 body,body 是 single source of truth。

字段说明

字段 必需 说明
code 固定 4008
message 人类可读的错误信息
server_version Server 当前支持的协议版本(Client 据此决定是否升级)
client_version Server 从 URL query 读取的客户端版本(回显供诊断)
min_supported Server 支持的最低协议版本
max_supported Server 支持的最高协议版本

SDK 实现要求

  • Client SDK 必须解析 HTTP 400 的响应 body,识别 code: 4008,转化为专属异常(如 ProtocolVersionError),禁止静默重试
  • 异常信息应明确告知用户两边版本,便于快速判断应升级哪端
  • 客户端 transport 顺序 SHOULD 配置为 polling → websocket(绝大多数 socketio 客户端默认即如此),保证首个握手请求是 HTTP polling,body 可访问
  • 可选:SDK 在本地日志中打印诊断信息(当前 SDK 声称的 PROTOCOL_VERSION 常量、接收到的 server_version)

Python 解析示例(reference impl)

import json
import socketio
from a2c_smcp import PROTOCOL_VERSION
from a2c_smcp.exceptions import ProtocolVersionError

sio = socketio.AsyncClient()
try:
    await sio.connect(
        f"wss://server.example.com?a2c_version={PROTOCOL_VERSION}",
        auth={"role": "agent", "agent_id": "..."},
    )
except socketio.exceptions.ConnectionError as e:
    # python-socketio 在 HTTP 非 2xx 时把 body 放进异常 message
    raw = str(e)
    try:
        body = json.loads(raw)
    except json.JSONDecodeError:
        raise  # 非协议级错误,保持原异常
    if body.get("code") == 4008:
        raise ProtocolVersionError(
            client_version=body.get("client_version"),
            server_version=body.get("server_version"),
            min_supported=body.get("min_supported"),
            max_supported=body.get("max_supported"),
        )
    raise

:协议规范仅提供 Python reference impl。其他 SDK(Rust / TypeScript / Go 等)的具体解析模式由各 SDK 自行决定——A2C-SMCP 协议文档不堆砌多语言示例。

详细的版本语义、兼容性规则与握手流程见 协议版本与握手


MCP 上游授权错误响应

MCP 协议自身定义了工具服务器的 OAuth 2.0 授权流程。A2C-SMCP 不介入该握手过程;本章仅定义授权结果如何向 Agent 反馈,使 Agent 能够区分"工具坏了"与"工具需要用户授权"。

完整的安全边界、Computer 实现要求与 Agent 行为约束,见 security.md → MCP 上游授权(OAuth 2.0 等) 本章仅承载响应结构与错误码,行为层约束以 security.md 为准,避免重复维护。

授权失败的响应结构

当 Computer 调用 MCP 工具因上游授权问题失败时,使用 CallToolResult 返回错误,并在 meta 中携带结构化提示:

CallToolResult(
    content=[TextContent(text="此工具需要授权 GitHub 账号后方可使用")],
    isError=True,
    meta={
        "error_code": 4006,  # 或 4007
        "mcp_server": "github-mcp",
        "auth_hint": {
            # 可选:引导用户完成授权的提示信息
            "action": "user_authorization_required",
            "message": "请在 Computer 宿主环境完成 GitHub OAuth 登录后重试"
        }
    }
)

4006/4007 判定决策表

为消除"未授权 vs 授权失败"的边界歧义,Computer MUST 按下表把上游 MCP Server / OAuth Provider 的具体表现映射到协议错误码:

上游表现 错误码 语义
Computer 从未为该 MCP Server 配置授权(无 token / 无 client credentials) 4006 用户首次授权
上游返回 HTTP 401 Unauthorized(凭证缺失、非法、未提供) 4006 用户重新授权
上游返回 HTTP 403 Forbidden(已登录但权限/scope 不足) 4007 已授权但能力不足,用户调整 scope 或换账号
Token 过期 + 刷新失败(refresh_token 无效 / 被撤销) 4007 历史授权失效,用户需重新走 OAuth 流程
用户已主动 revoke OAuth 授权 4007 历史授权已被撤销
凭证存在但 scope 不足(上游未明确返回 401/403,但断言权限不够) 4007 同 403 类,归"已授权但失败"
OAuth provider 自身故障(5xx / 网络) 4003 Tool Execution Failed 不属于授权语义,按通用工具失败处理

简明判别

  • 区分点是"用户是否曾经授权"——4006 = 没授权过 / 凭证不存在;4007 = 曾授权但当前不可用
  • 当 Computer 无法可靠判别时,倾向于报 4006(提示用户重新走授权流程是稳妥兜底)

Agent 行为差异:Agent 收到 4006 应引导用户首次/重新完成授权;收到 4007 应引导用户检查权限设置或重新授权——区别仅在 UX 文案,机器路由可统一。

字段说明

字段 强度 说明
meta.error_code MUST 4006(未授权)或 4007(授权失效),按上方判定决策表映射
meta.mcp_server MUST 触发授权错误的 MCP Server 标识,便于 Agent 定位
meta.auth_hint SHOULD 面向用户的非敏感提示对象。Computer SHOULD 提供以协助 Agent/Host 引导用户;缺失时 Agent 仍能基于 error_code 做兜底处理
meta.auth_hint.action MAY 机器可读动作标识(如 user_authorization_required / token_refresh_required),便于 Host 路由到不同 UI
meta.auth_hint.message SHOULD 用户可读的一句话引导(如"请在 Computer 宿主环境完成 GitHub OAuth 登录后重试");强烈建议提供以满足最小可用 UX

auth_hint 安全边界

auth_hint 是协议层唯一面向 Agent 暴露的授权相关字段。为防止凭证经此通道泄漏给 Agent,Computer MUST NOTauth_hint(任何子字段)中包含以下任何一类内容:

类别 禁止字段示例
访问凭证 access_token / refresh_token / id_token / bearer_token / 任何形式的 Token
OAuth 流程参数 code / code_verifier / code_challenge / state / nonce
客户端凭证 client_id / client_secret / assertion / client_assertion
完整授权 URL 任何包含上述 query 参数的完整 URL(即使已经 url-encode 也禁止)
用户敏感数据 用户密码、TOTP/OTP、安全问题答案

允许包含:

  • 自然语言描述("需要登录 GitHub 后重试")
  • MCP Server 名称、工具名称
  • 不含敏感参数的着陆页 URL(如 https://example.com/login,无 query)

完整凭证传播禁令见 security.md → 零凭证传播原则auth_hint 是该原则下的唯一豁免——豁免范围严格限于上表"允许包含"部分。

MCP Server Not Found(4014)

触发时机:客户端事件(client:get_resources / client:tool_call 等)引用的 mcp_server 名字在 Computer 上未注册。

响应结构(Socket.IO ack 数据):

{
  "code": 4014,
  "message": "MCP Server not registered",
  "mcp_server_name": "com.example.docs"
}

字段说明

字段 必需 说明
code 固定 4014
message 人类可读
mcp_server_name 客户端引用的 server 名

Agent 行为建议:刷新 server list(调用 client:get_config)后重试;持续不存在则提示用户 server 已下线。

MCP Capability Not Supported(4015)

触发时机:MCP Server 已注册,但未声明所请求事件依赖的 capability。例如 client:get_resources 调用时该 server 未声明 resources capability。

响应结构(Socket.IO ack 数据):

{
  "code": 4015,
  "message": "MCP Server does not support 'resources' capability",
  "mcp_server_name": "com.example.docs",
  "capability": "resources"
}

字段说明

字段 必需 说明
code 固定 4015
message 人类可读
mcp_server_name 目标 server
capability 缺失的 capability 名(resources / tools / prompts 等 MCP 标准 capability)

Agent 行为建议:跳过此 server,不再向其发送同类事件;可在 server list UI 中标注能力缺失。

Invalid Skill Name(4016)

触发时机client:get_skill 入参 name 不符合 SKILL name lexer 规则(skill.md §1.4)——段数 ∉ {1, 2, 3}、3 段但首段 ≠ mcp、或某段字符集非法(leaf 段须符合 marketplace SKILL v1 §3.1)。注意 user 源裸名为合法 1 段、无 :,不因「缺 :」判错。属参数校验类硬错:Computer 在进入 Skill Registry 查询路径之前即拒绝(防止从 name 推导 FS 路径越权)。

响应结构(Socket.IO ack 数据):

{
  "code": 4016,
  "message": "Invalid skill name format",
  "details": { "name": "bad name!!" }
}

字段说明

字段 必需 说明
code 固定 4016
message 人类可读
details.name 被拒的原始 name,仅供诊断(Agent MUST NOT 透传给最终用户)

与 4014 的边界name 格式合法但不存在(未注册 / 已卸载 / 孤儿)→ 复用 4014name 格式非法4016。两者互斥:Computer 先做 lexer 校验(不过则 4016),通过后再查 Registry(未命中则 4014)。

Agent 行为建议4016 属客户端构造错误,不应重试同一 name;应回溯 client:get_skills 返回的 A2CSkillRef.name 原样使用,不要自行拼接 / 改写。

Skill Resource Not Accessible(4017)

触发时机client:get_skillname 合法且命中 Registry,但 rel_path 无法安全服务。Computer 在 Registry 解析出包根后、读取文件前完成校验(包根绝对路径来源唯一是 Registry,禁止name / rel_path 推导)。

判定rel_path 缺省 SKILL.mdsafe_join(包根, rel_path)realpath 必须仍落在包根内。下列任一 → 4017

details.reason 触发
traversal rel_path 为绝对路径、含 ..、或符号链接逃逸出包根
forbidden 命中敏感文件(.skillenv 及 Computer 判定的凭证类文件)——无论是否存在一律按 forbidden,MUST NOT 泄漏存在性
not_found 路径合法且在包根内,但目标文件不存在
too_large 资源总字节数超过 SDK 可配的绝对上限——首块前即拒,零字节传输,带 details.total_size

响应结构(Socket.IO ack 数据):

{
  "code": 4017,
  "message": "Skill resource not accessible",
  "details": { "reason": "traversal", "rel_path": "../../etc/passwd" }
}

字段说明

字段 必需 说明
code 固定 4017
message 人类可读
details.reason traversal / forbidden / not_found / too_large(开放枚举,未来可非破坏新增)
details.rel_path 被拒的原始 rel_path,仅供诊断(Agent MUST NOT 透传给最终用户)
details.total_size too_large:资源总字节数,供 Agent 告知用户/降级决策

安全不变量forbidden 对"文件不存在"与"文件存在但敏感"返回同一 reason,不暴露敏感文件存在性。.skillenv 在任何 rel_path 下都不可经此通道读出——这是 SKILL 通道 §9 安全模型 的硬约束,不是可选项。

Agent 行为建议traversal / forbidden 属客户端构造错误,不重试not_found 说明 SKILL.md 披露的路径与实际包内容不一致,可回退到仅用 SKILL.md body;too_large 不重试,依 details.total_size 向用户说明或改用其它策略(该资源不经此通道传输)。rel_path 应严格取自 SKILL.md 正文披露的引用,不自行拼接。

成功路径:解析通过且未超上限时,文本可内联走 body;二进制 / 过大文本则 get_skill 铸造 blob_handle,字节经 client:get_blob 拉取(拉取期错误见 4018)。

Blob Not Accessible(4018)

触发时机client:get_blob 拉取阶段——blob_handle 无法解析回一个当前仍被铸造通道授权的可读源。资源的鉴权与绝对上限已在铸造期由生产者通道决断(不通过则根本没有句柄),故 4018 只承载拉取期失败。

判定

details.reason 触发
invalid_handle blob_handle 格式非法 / 非本 Computer 铸造 / 无法识别
forbidden 句柄可解析,但重施铸造通道鉴权失败(如 SKILL 现已 orphan、.skillenv 等仍禁止)——防御纵深,句柄内容绝不被直接信任
gone 源已不可达:SKILL 卸载 / 文件删除 / 内容已无法服务
range chunk_offset < 0> total_size

响应结构(Socket.IO ack 数据):

{
  "code": 4018,
  "message": "Blob not accessible",
  "details": { "reason": "gone" }
}

字段说明

字段 必需 说明
code 固定 4018
message 人类可读
details.reason invalid_handle / forbidden / gone / range(开放枚举,未来可非破坏新增)

安全不变量client:get_blob 不是任意文件读原语。Computer 每次解析句柄 MUST 重跑铸造通道的边界校验(SKILL → §9 沙箱);句柄即便编码了路径也绝不被直接信任。.skillenv 等敏感文件因在铸造期已被 4017 forbidden 拦截,永不会有指向它的句柄。

Agent 行为建议invalid_handle / forbidden 不重试;gone 回到生产者通道重新获取(如重新 client:get_skill 取新 blob_handle);range 修正 chunk_offset 后重试。跨块若 sha256 / total_size 变化,从 offset 0 重读。

TODO

以下功能尚未实现,计划在后续版本中完善:

  • 统一的错误码定义文件
  • 错误码国际化支持
  • 错误追踪 ID(trace_id)
  • 错误统计和监控接口
  • 工具元数据层面的 requires_auth 标注(见 security.md

参考

  • MCP CallToolResult: https://github.com/modelcontextprotocol/specification
  • Socket.IO 错误处理: https://socket.io/docs/v4/handling-errors/