跳转至

A2C-SMCP 事件定义

本文档定义了 A2C-SMCP 协议的所有事件,基于 a2c_smcp/smcp.py 中的实际实现。

命名空间

所有事件在以下命名空间中传输:

SMCP_NAMESPACE = "/smcp"

事件分类规则

前缀 方向 说明 实现要求
client: Agent → Server → Computer 工具操作类,由 Server 路由到指定 Computer Computer 必须实现
server: 客户端 → Server 房间管理、状态更新类 Server 必须实现
notify: Server → 广播 通知类,由 Server 广播到房间 Agent/Computer 选择性接收

连接握手

Agent 或 Computer 在通过 Socket.IO 连接到 Server 时分两个位置携带参数:

  1. URL query 中的 a2c_version(协议版本号)—— 由 Server 在 HTTP 中间件层校验,先于 Socket.IO 处理
  2. auth 对象中的 role(业务身份)—— 由 Server 在 connect handler 中处理

参数位置

位置 字段 必需 说明
URL query a2c_version 协议版本号,如 0.2.0
auth 对象 role "agent""computer"

客户端连接示例

# Python 客户端
import socketio
from a2c_smcp import PROTOCOL_VERSION

sio = socketio.AsyncClient()
await sio.connect(
    f"wss://server.example.com?a2c_version={PROTOCOL_VERSION}",
    socketio_path="/smcp",
    auth={"role": "agent"},
)
// TypeScript 客户端
import { io } from "socket.io-client";
import { PROTOCOL_VERSION } from "@a2c-smcp/client";

const socket = io("wss://server.example.com", {
  path: "/smcp",
  query: { a2c_version: PROTOCOL_VERSION },
  auth: { role: "agent" },
});

Server 校验行为

版本校验必须在 HTTP 中间件层完成,不能依赖 Socket.IO 的 connect handler。这保证业务代码无法影响协议校验的正确性。

  1. 缺失 a2c_version → HTTP 400,body {"code": 400, "message": "Missing a2c_version query parameter"}
  2. a2c_version 格式非法 → HTTP 400,body {"code": 400, "message": "Invalid a2c_version: ..."}
  3. a2c_version 与 Server 不兼容 → HTTP 400,body 见 4008 Protocol Version Mismatch
  4. 校验通过 → Server 将 a2c_version 存入 Socket.IO session,供 server:list_room 查询使用;连接进入 Socket.IO 层,connect handler 处理 auth.role

校验规则(MAJOR.MINOR.PATCH 语义、v0.x 与 v1.0+ 的区别、中间件实现示例)详见 协议版本与握手

客户端错误处理

Socket.IO 客户端遇到 HTTP 400 会触发 connect_error 事件并携带响应 body。SDK 必须解析 body、识别 code: 4008 并抛出专属异常:

import json

@sio.on("connect_error")
async def on_connect_error(data):
    payload = data if isinstance(data, dict) else (
        json.loads(data) if isinstance(data, str) else None
    )
    if payload and payload.get("code") == 4008:
        raise ProtocolVersionError(
            server_version=payload.get("server_version"),
            client_version=payload.get("client_version"),
        )

完整事件列表

Client 事件(Agent → Computer)

这些事件由 Agent 发起,通过 Server 路由到指定的 Computer 执行。

事件常量 事件名称 描述 请求数据结构 响应数据结构
TOOL_CALL_EVENT client:tool_call 工具调用请求 ToolCallReq CallToolResult
GET_CONFIG_EVENT client:get_config 获取 Computer 配置 GetComputerConfigReq GetComputerConfigRet
GET_TOOLS_EVENT client:get_tools 获取工具列表 GetToolsReq GetToolsRet
GET_DESKTOP_EVENT client:get_desktop 获取桌面信息 GetDeskTopReq GetDeskTopRet
GET_DPE_EVENT client:get_dpe 把 DPE URI 解析为 Agent 可访问的 URI GetDPEReq GetDPERet
GET_RESOURCES_EVENT client:get_resources 透明转发指定 MCP Server 的 resources/list(含 cursor 翻页) GetResourcesReq GetResourcesRet

Server 事件(客户端 → Server)

这些事件由 Agent 或 Computer 发起,由 Server 处理。

事件常量 事件名称 发起方 描述 数据结构
JOIN_OFFICE_EVENT server:join_office Agent/Computer 加入房间 EnterOfficeReq
LEAVE_OFFICE_EVENT server:leave_office Agent/Computer 离开房间 LeaveOfficeReq
UPDATE_CONFIG_EVENT server:update_config Computer 配置更新通知请求 UpdateComputerConfigReq
UPDATE_TOOL_LIST_EVENT server:update_tool_list Computer 工具列表更新通知请求 UpdateToolListNotification
UPDATE_DESKTOP_EVENT server:update_desktop Computer 桌面更新通知请求 UpdateComputerConfigReq
CANCEL_TOOL_CALL_EVENT server:tool_call_cancel Agent 取消工具调用 AgentCallData
LIST_ROOM_EVENT server:list_room Agent 列出房间内所有会话 ListRoomReq

Notify 事件(Server → 广播)

这些事件由 Server 广播到房间内的所有成员。

事件常量 事件名称 描述 数据结构
ENTER_OFFICE_NOTIFICATION notify:enter_office 成员加入房间通知 EnterOfficeNotification
LEAVE_OFFICE_NOTIFICATION notify:leave_office 成员离开房间通知 LeaveOfficeNotification
UPDATE_CONFIG_NOTIFICATION notify:update_config 配置更新通知 UpdateMCPConfigNotification
UPDATE_TOOL_LIST_NOTIFICATION notify:update_tool_list 工具列表更新通知 UpdateToolListNotification
UPDATE_DESKTOP_NOTIFICATION notify:update_desktop 桌面更新通知 UpdateComputerConfigReq
CANCEL_TOOL_CALL_NOTIFICATION notify:tool_call_cancel 工具调用取消通知 AgentCallData

事件详细说明

工具调用事件

client:tool_call

Agent 向指定 Computer 发起工具调用。

请求流程:

Agent ──[client:tool_call]──→ Server ──[转发]──→ Computer
Agent ←──────────────────────────────────────── 返回结果

请求数据 (ToolCallReq):

{
    "agent": str,       # Agent 标识
    "req_id": str,      # 请求 ID(用于去重和取消)
    "computer": str,    # 目标 Computer 名称
    "tool_name": str,   # 工具名称
    "params": dict,     # 工具调用参数
    "timeout": int      # 超时时间(秒)
}

响应: 返回 MCP CallToolResult 结构。

client:get_tools

获取指定 Computer 的可用工具列表。

请求数据 (GetToolsReq):

{
    "agent": str,       # Agent 标识
    "req_id": str,      # 请求 ID
    "computer": str     # 目标 Computer 名称
}

响应数据 (GetToolsRet):

{
    "tools": list[SMCPTool],  # 工具列表
    "req_id": str             # 请求 ID
}

Agent 端解析 meta 的注意事项

SMCPTool.meta 中的 a2c_tool_meta 字段值为 JSON 字符串,需要 json.loads() 后 才能访问 tagsauto_apply 等字段。详见 SMCPTool.meta 序列化规范

client:get_desktop

获取指定 Computer 的桌面信息(窗口资源聚合视图)。

请求数据 (GetDeskTopReq):

{
    "agent": str,           # Agent 标识
    "req_id": str,          # 请求 ID
    "computer": str,        # 目标 Computer 名称
    "desktop_size": int,    # 可选:限制返回的桌面内容数量
    "window": str           # 可选:指定获取的 WindowURI
}

响应数据 (GetDeskTopRet):

{
    "desktops": list[str],  # 桌面内容列表
    "req_id": str           # 请求 ID
}

client:get_dpe

把一个 DPE URI 转成 Agent 可访问的 URI。Computer 调本地注册的 DPE Resolver hook 完成转换;Resolver 业务自决(上传对象存储 / 落本地缓存 / 任意 URI scheme)。Agent 拿到访问 URI 后用应用层协议(HTTP / file / ...)自取实际内容——A2C 协议承载 DPE 内容。

请求数据 (GetDPEReq):

{
    "agent": str,                # Agent 标识
    "req_id": str,               # 请求 ID
    "computer": str,             # 目标 Computer 名称
    "uri": str,                  # dpe://host/doc-ref(doc-ref 可单段或分段路径)
    "timeout": int               # 可选:秒,默认实现自定
}

路由说明:DPE URI 是自包含寻址凭据——Computer 通过 URI 中的 host 反查目标 MCP Server。host 跨 Server SHOULD 唯一(非 MUST);冲突时 Computer 在注册阶段记 WARN、路由按"先注册优先"。详见 DPE 文档协议 - host 路由策略

响应数据 (GetDPERet):

{
    "uri": str,                  # Resolver 输出的访问 URI(任意 scheme:https / file / 业务自定义)
    "mime_type": str,            # 可选
    "size": int,                 # 可选:字节数
    "req_id": str
}

错误码:未注册 Resolver → 4011 DPE Resolver Not Configured;URI 非法 → 4012 Invalid DPE URI;Resolver 执行失败(含 host 反查无结果、上游 MCP Server 不可用、上传异常等) → 4013 DPE Resolution Failed

详见 DPE 文档协议

client:get_resources

枚举指定 MCP Server 暴露的 Resource 列表——透明转发 MCP 标准 resources/list不做协议级过滤。Agent 据此发现 dpe / window / 业务自定义 scheme 的资源。

支持 MCP 标准 cursor 翻页:Agent 首次调用不带 cursor,响应中含 next_cursor 时继续翻页;无 next_cursor 表示已到末尾。

请求数据 (GetResourcesReq):

{
    "agent": str,           # Agent 标识
    "req_id": str,          # 请求 ID
    "computer": str,        # 目标 Computer 名称
    "mcp_server": str,      # 必填:目标 MCP Server 名称(来自 client:get_config 返回的 servers 字典 key)
    "cursor": str           # 可选:MCP 标准 cursor 翻页;首次不传或传 null
}

响应数据 (GetResourcesRet):

{
    "resources": list[Resource],  # MCP 标准 Resource 列表(含任意 scheme,业务自决过滤)
    "next_cursor": str,           # 可选:有则继续翻页;无则结束
    "req_id": str
}

Computer 处理流程

  1. 校验 mcp_server 是否在已注册的 MCP Server 列表中——不存在返回 404 Not Found(错误信息含 mcp_server 名称)
  2. 透明转发到对应 MCP Server 的 resources/list(cursor=...)
  3. 直接返回 {resources, next_cursor}不做任何 scheme / 元数据层面的过滤

典型 Agent 使用流程(DPE 文档发现):

1. Agent → client:get_config(computer)
   ← 拿到所有 MCP Server 配置(servers 字典 key 即 server names)

2. 对每个 server name:
   Agent → client:get_resources(mcp_server=name, cursor=None)
   ← {resources, next_cursor}
   while next_cursor: 继续翻页

3. Agent 自己过滤(按 scheme / _meta / annotations / 名称等)

4. Agent → client:get_dpe(uri=dpe://...) → 拿到访问 URI → 应用层 fetch

设计原则

  • Computer 是 MCP 标准 resources/list透明转发层——不做 scheme / 元数据过滤、不做跨 Server 聚合
  • 保留 MCP 标准 cursor 翻页能力——Agent 按需翻页,不强制全量加载
  • 业务方拿到 Resource 后自决过滤逻辑(dpe:// / window:// / 业务自定义 scheme / _meta 字段 / 名称匹配)

详见 DPE 文档协议 - 文档发现

client:get_config

获取指定 Computer 的完整配置信息。

请求数据 (GetComputerConfigReq):

{
    "agent": str,       # Agent 标识
    "req_id": str,      # 请求 ID
    "computer": str     # 目标 Computer 名称
}

响应数据 (GetComputerConfigRet):

{
    "inputs": list[MCPServerInput] | None,  # 输入定义列表
    "servers": dict[str, MCPServerConfig]   # MCP Server 配置映射
}


房间管理事件

server:join_office

Agent 或 Computer 请求加入房间。

请求数据 (EnterOfficeReq):

{
    "role": Literal["computer", "agent"],  # 角色类型
    "name": str,                           # 名称
    "office_id": str                       # 房间 ID
}

响应: (bool, str | None) - 成功标志和错误信息。

Server 处理规则: - Agent: 检查房间是否已有其他 Agent,若有则拒绝 - Computer: 若已在其他房间,自动离开旧房间

server:leave_office

Agent 或 Computer 请求离开房间。

请求数据 (LeaveOfficeReq):

{
    "office_id": str    # 房间 ID
}

server:list_room

Agent 查询指定房间内的所有会话信息。

请求数据 (ListRoomReq):

{
    "agent": str,       # Agent 标识
    "req_id": str,      # 请求 ID
    "office_id": str    # 房间 ID
}

响应数据 (ListRoomRet):

{
    "sessions": list[SessionInfo],  # 会话列表
    "req_id": str                   # 请求 ID
}

其中 SessionInfo:

{
    "sid": str,                             # 会话 ID
    "name": str,                            # 会话名称
    "role": Literal["computer", "agent"],   # 角色
    "office_id": str,                       # 所属房间 ID
    "a2c_version": str                      # 协议版本号(Server 从 URL query 记录)
}

peer 版本可见性

a2c_version 由 Server 在 HTTP 握手校验阶段从 URL query 提取并存入 session。同一房间内所有成员的 a2c_version 必然兼容(Server 已在 HTTP 层校验),因此该字段不用作二次校验,仅用于展示与诊断:

  • UI 侧显示房间成员的版本
  • 日志/监控侧便于故障定位

详见 协议版本与握手


配置与状态更新事件

server:update_config

Computer 通知 Server 其配置已更新,Server 随后广播 notify:update_config

请求数据 (UpdateComputerConfigReq):

{
    "computer": str     # Computer 名称
}

server:update_tool_list

Computer 通知 Server 其工具列表已更新,Server 随后广播 notify:update_tool_list

请求数据 (UpdateToolListNotification):

{
    "computer": str     # Computer 名称
}

server:update_desktop

Computer 通知 Server 其桌面内容已更新,Server 随后广播 notify:update_desktop

触发条件(由 Computer 端检测):

  • MCP Server 发出 ResourceListChangedNotificationwindow:// URI 集合发生变化
  • MCP Server 发出 ResourceUpdatedNotification 且目标 URI 为 window://

请求数据: 复用 UpdateComputerConfigReq(与 server:update_config 共享同一数据结构):

{
    "computer": str     # Computer 名称
}

Server 处理: 接收后向该 Computer 所在房间广播 notify:update_desktop

详见 Desktop 桌面系统 中的 更新机制


通知事件

notify:enter_office

Server 广播:有成员加入房间。

数据结构 (EnterOfficeNotification):

{
    "office_id": str,
    "computer": str | None,  # 加入的 Computer 名称(若为 Computer)
    "agent": str | None      # 加入的 Agent 名称(若为 Agent)
}

Agent 响应建议: 收到此通知后,应自动调用 client:get_tools 获取新 Computer 的工具列表。

notify:leave_office

Server 广播:有成员离开房间。

数据结构 (LeaveOfficeNotification):

{
    "office_id": str,
    "computer": str | None,  # 离开的 Computer 名称
    "agent": str | None      # 离开的 Agent 名称
}

notify:update_config

Server 广播:某 Computer 的配置已更新。

数据结构 (UpdateMCPConfigNotification):

{
    "computer": str     # 更新配置的 Computer 名称
}

Agent 响应建议: 收到此通知后,可调用 client:get_config 获取最新配置。

notify:update_tool_list

Server 广播:某 Computer 的工具列表已更新。

数据结构 (UpdateToolListNotification):

{
    "computer": str     # 更新工具列表的 Computer 名称
}

Agent 响应建议: 收到此通知后,应调用 client:get_tools 刷新本地工具列表。

notify:update_desktop

Server 广播:某 Computer 的桌面内容已更新。

数据结构: 复用 UpdateComputerConfigReq(与 notify:update_config 结构一致):

{
    "computer": str     # 桌面发生变化的 Computer 名称
}

Agent 响应建议: 收到此通知后,推荐自动调用 client:get_desktop 获取最新桌面。

详见 Desktop 桌面系统 中的 完整生命周期时序图

notify:tool_call_cancel

Server 广播:某工具调用已被取消。

数据结构 (AgentCallData):

{
    "agent": str,   # 发起取消的 Agent
    "req_id": str   # 被取消的请求 ID
}


事件流程图

工具调用流程

sequenceDiagram
    participant A as Agent
    participant S as Server
    participant C as Computer

    A->>S: client:tool_call (ToolCallReq)
    S->>C: client:tool_call (转发)
    C->>S: 工具执行结果
    S->>A: 返回结果

动态工具发现流程

sequenceDiagram
    participant A as Agent
    participant S as Server
    participant C as Computer

    Note over C: Computer 加入房间
    C->>S: server:join_office
    S->>A: notify:enter_office
    A->>S: client:get_tools
    S->>C: client:get_tools (转发)
    C->>S: 工具列表
    S->>A: GetToolsRet
    Note over A: 注册新工具

配置更新流程

sequenceDiagram
    participant A as Agent
    participant S as Server
    participant C as Computer

    Note over C: 配置变更
    C->>S: server:update_config
    S->>A: notify:update_config
    A->>S: client:get_tools (可选)
    S->>C: 转发
    C->>S: 工具列表
    S->>A: GetToolsRet

桌面更新流程

sequenceDiagram
    participant M as MCP Server
    participant C as Computer
    participant S as Server
    participant A as Agent

    Note over M: 窗口资源变化
    M->>C: ResourceListChangedNotification
    C->>C: 比较 window:// URI 集合
    C->>S: server:update_desktop
    S->>A: notify:update_desktop
    A->>S: client:get_desktop
    S->>C: client:get_desktop (转发)
    C->>C: organize_desktop()
    C->>S: GetDeskTopRet
    S->>A: GetDeskTopRet

详见 Desktop 桌面系统

DPE 文档访问流程

sequenceDiagram
    participant M as MCP Server
    participant C as Computer
    participant S as Server
    participant A as Agent
    participant Store as 业务存储<br/>(OSS / 本地 / etc)

    Note over A: Agent 通过任意方式得到 dpe URI
    A->>S: client:get_dpe(dpe://host/doc-1)
    S->>C: 路由到目标 Computer
    C->>M: resources/read(dpe://host/doc-1)
    M->>C: ResourceContents
    C->>C: 调 DPE Resolver Hook(业务实现)
    C->>Store: 上传 / 持久化(业务自决)
    Store->>C: 访问 URI
    C->>S: GetDPERet(uri=https://...)
    S->>A: GetDPERet
    A->>Store: 用应用层(HTTP/file/...)拉取实际内容

详见 DPE 文档协议


实现要求

Server 必须实现

  • 所有 server:* 事件的处理
  • 所有 client:* 事件的路由转发
  • 所有 notify:* 事件的广播

Computer 必须实现

  • 所有 client:* 事件的处理(作为接收方)
  • 房间管理事件 (server:join_office, server:leave_office)

Agent 应该实现

  • notify:enter_office - 自动获取新 Computer 的工具
  • notify:leave_office - 清理离开 Computer 的工具
  • notify:update_config / notify:update_tool_list - 刷新工具列表

参考

  • 事件常量定义: a2c_smcp/smcp.py
  • Server 实现: a2c_smcp/server/namespace.py
  • Agent 客户端: a2c_smcp/agent/client.py
  • Computer 客户端: a2c_smcp/computer/socketio/client.py