系统中每一项可以被 Agent 调用的业务能力,均被定义为一个 Tool。Tool 是动作契约在 AI 侧的投影: 同一项业务能力在 Web 控制台、内嵌 AI 助手与 MCP 通道下共用同一份实现, Tool 只负责把它暴露给调用方,并声明调用它所需满足的条件。
一个 Tool 由五项要素构成:名称、说明、注解、入参模式与处理函数。接口定义如下。
五项要素各自承担一项职责。名称形如 equipment:list,前段为业务域,后段为动作,
在注册表中全局唯一。说明供模型判断调用时机,其写法要求可判读,需写明筛选维度与返回内容。
注解声明该工具的权限要求与超时上限。入参模式以 Zod 声明,运行时转换为 JSON Schema 交给客户端,
因此客户端的参数校验范围与服务端一致。处理函数接收入参与授权上下文,返回统一的结果结构。
权限要求由工具自身声明,这一点值得单独说明。调用方不需要传入权限信息, 注册表也不必执行任何业务逻辑,即可判断某位调用者能否使用该工具。 该性质是第 02 节所述清单过滤的实现基础。
需要与普通接口区分的是:普通接口只描述输入与输出,调用方是否可以使用由接口之外的约定决定; Tool 把这一约定写进自身,使可见性成为一项可计算的事实。其直接结果是工具的可用范围不需要逐项人工维护, 也不会因新增一条通道而失效。命名采用业务域加动作的形式,按域分组后可直接对应到业务模块, 便于在核验时逐域核对。
依据:tools/<模块>/<名称>.tool.ts 的接口定义与 ai/tool-registry.ts 的工具类型声明。
Tool Registry 的实现共 86 行,职责只有两项:存放已注册的工具,以及按权限生成可见清单。
registerAll 在启动时一次性写入全部工具;若两次注册使用同一名称,直接抛出错误,
避免两处实现覆盖同一项能力。get 按名称取回单条工具,供调用执行阶段使用。
getAll 返回全部工具,供启动日志与运维核对使用。
其中承担权限职责的是 getAuthorized。它接收一个 CASL 能力对象,
逐条判断工具注解中声明的权限要求是否成立;未声明权限要求的工具被视为通用工具,对所有调用者开放。
因此注册表既不持有身份信息,也不参与身份认证——身份由各条通道在进入注册表之前确定,
注册表只负责依据身份裁剪清单。
该实现的边界同样明确。注册表不做身份认证,不访问数据库,也不判断业务状态; 它只回答一个问题:在某位调用者的权限下,哪些工具应当出现在清单里。 把职责限制在这一层,使注册表可以在任意通道中被复用,也使其行为可以在单元测试中直接断言, 不必为验证权限过滤而准备数据库与环境依赖。
权限过滤发生在清单生成阶段,而非调用执行阶段。 调用方收到的是「该身份可以使用的工具集合」,而不是「全部工具加上逐条拒绝」。 两者的差别在于前者的边界在协议层即已确定:客户端看不到未授权工具的名称、说明与入参模式,因而无法构造调用。
依据:ai/tool-registry.ts(86 行)与 tools/register.ts 的实现记录;权限注解取值为 CASL 的 action 与 subject。
注册入口集中在 tools/register.ts,启动时调用一次。
新增一项工具需要两步:在 tools/<模块>/<名称>.tool.ts 创建实现,
在 register.ts 增加一行 import 与一行注册。注册表本身与 MCP 适配层均不需要修改。
该结构的实际意义在于扩展成本固定。新增业务能力不会触动已注册的其他工具,改动范围与工具总数无关。 这一性质在工具数量较多的系统中会持续产生收益:注册表是唯一需要维护的清单, 新增动作时不必在多个位置同步信息,也就不会出现某处已更新、另一处仍指向旧实现的情形。
注册完成后输出一行日志,报出当前注册的工具总数。该数值可在启动阶段与预期值核对, 用于发现模块未加载或注册遗漏两类问题。
重名直接抛出错误而不作告警处理,原因在于同名工具意味着同一项业务能力存在两份实现。 若允许后者覆盖前者,故障形态为某条通道的行为与其他通道不一致,且难以在启动阶段被察觉。 启动即失败可以避免该类问题进入运行期。
依据:tools/register.ts(30 行)的注册入口设计与 ai/tool-registry.ts 的启动日志记录。
以设备列表查询为例,说明一项具体工具的写法。
该实现有四处需要说明。
ctx.ability 生成筛选条件,同一条工具在集团角色与子公司角色下返回的数据范围不同,差异由权限裁决产生,工具自身不写死范围。success、data 与 cardType。其中 cardType 供内嵌 AI 助手选择呈现方式,MCP 通道只取其中的 data。超时上限单独声明,用于区分响应时间差别较大的工具。查询类工具的返回行数随筛选条件变化, 其超时上限通常大于单条记录的读写。该取值按工具设定,注册表不在逐项设置之外另设统一值。
| 要素 | 取值 | 作用 |
|---|---|---|
| 名称 | equipment:list | 业务域与动作的组合,在注册表中全局唯一 |
| 说明 | 搜索设备列表,支持按名称、资产编码、型号、位置、运行状态筛选 | 供模型判断调用时机 |
| 权限注解 | read / Equipment | 注册表据此判断该工具是否进入清单 |
| 超时 | 10 000 毫秒 | 覆盖默认的 15 秒,适用于返回行数可能较多的查询 |
| 入参模式 | 关键字、运行状态、部门、页码、每页条数 | 客户端参数校验与服务端保持一致 |
| 返回结构 | success / data / cardType | 内嵌助手取 cardType 呈现,MCP 通道取 data |
该写法同样适用于清单、工单与报表类工具。区别只在权限注解的取值与返回结构中 data 的字段,
工具与注册表之间的接口保持不变。
依据:tools/equipment/list.tool.ts 的实现;入参校验与数据范围条件取自 CASL 权限裁决结果。
MCP 适配层 mcp/server.ts 共 66 行,向协议层提供两个入口:生成工具清单,以及执行工具调用。
两者均以已授权的调用上下文为输入。
generateMcpToolList 先调用 getAuthorized 生成该身份的可见清单,
再转换为 MCP 协议要求的格式。清单在服务端生成,与客户端种类无关——
同一凭证据在两个不同客户端中列出的工具应当一致。未列入清单的工具,
客户端不会取得其名称、说明与入参模式,因而无法构造合法调用。
executeMcpToolCall 按名称取回工具并执行处理函数。名称不存在时返回错误标记;
处理函数返回失败时同样返回错误标记,并携带错误信息。两种情形均由客户端原样呈现,服务端不做改写。
清单生成本身不足以构成权限控制,因此执行阶段仍需按名称取回工具。两者的分工是: 清单决定客户端能看到什么,执行入口决定服务端如何处理一次具体请求。 客户端可以自行构造请求,但无法凭清单之外的名称取回工具。
| 入口 | 输入 | 输出的权限形态 |
|---|---|---|
generateMcpToolList |
已授权的调用上下文 | 仅包含该身份可见的工具;未列入者客户端不可见,无法取得入参定义 |
executeMcpToolCall |
工具名称与入参 | 按名称取回工具并执行;名称不存在时返回错误标记,不执行任何业务逻辑 |
权限边界由清单决定。以演示账号接入后,由客户端列出可用工具,核对返回的条目数量与业务域分组。 若清单中不含写入类动作,说明这些动作未被列入,而非在调用时被逐条拦截。 查看核验步骤 →
依据:mcp/server.ts(66 行)的协议适配实现;该账号可见工具数以端点实时返回为准。
同一项 Tool 可以被三类消费者调用。三者的入口不同,权限检查发生的位置也不同。
| 消费方 | 入口 | 权限检查位置 |
|---|---|---|
| 内部 AI EAMX 内嵌助手 |
Tool Factory → mcpToolToAiSdkTool() |
Tool Factory 的 execute 内 |
| 外部 AI Claude Desktop / Cursor 等 |
MCP Server → generateMcpToolList() |
getAuthorized() 生成清单时按权限过滤 |
| HTTP API | Express Router → 中间件 | API 中间件 |
三个入口的检查位置不同,裁决依据相同:均取自同一套 CASL 规则。 工具自身不承担权限判断,只在注解中声明其要求。因此权限口径不存在第二套写法, 也不会出现某一通道可以执行、另一通道不可执行的情形。
三条路径共用同一批工具,另一层含义在于:为界面开发的业务能力不需要为 Agent 单独重写一遍。 新增一项动作时,三条通道同时获得该能力,改动仍集中在注册入口一处。
将这一层放回系统整体,其上游关系如下。动作契约共 247 条,覆盖 35 个业务域,是工具注册的源清单。
权限裁决来自 CASL 同构权限设计,前后端共用同一套规则。写入类动作统一经过 executeAction 管道,
在管道内依次完成参数校验、权限裁决、预演、人工确认、幂等键、执行与审计七道环节,
与调用方是 Web 控制台还是外部 Agent 无关。七道环节的顺序固定,任一条通道的调用都不会跳过其中的任何一环,
因此权限与审计的口径不因接入方式而改变。
演示账号的工具清单由端点实时返回。该数值是权限裁剪的结果:写入类动作不在只读角色的清单内。 Agent 不因接入而取得更高权限,亦不存在可以绕过的路径。
依据:动作契约清单(247 条 / 35 个业务域)、executeAction 管道的七道环节与 CASL 同构权限设计;该账号可见工具数以端点实时返回为准。
讨论开放接口时,能力清单通常被放在最前面。对信息中心与技术评估而言,下述三类信息比能力清单更早被追问。
| 事项 | 说明 |
|---|---|
| 演示账号不可见什么 | 全部写入类动作不在演示账号的工具清单内。以权限设计划定边界,而非在调用时逐条拦截 |
| 哪些动作不得自主执行 | 改账、报废、权限变更等高危动作在动作定义上标注 confirm,未携带确认一律拒绝执行 |
| 不承诺什么 | 不承诺识别准确率 100%,不承诺完全无人值守 |
工具数量并不固定。该数值随版本迭代变化,站内不写固定值,核验时以端点实时返回为准; 核验时以端点实时返回的清单为准。演示凭证由使用者在端点自行领取,页面不留固定值—— 固定值无法轮换,也无法按使用者分别限流与审计,因此不写入页面。
上述三项均属可在接入前确认的事项。工具清单与业务域覆盖可由客户端当场报出; 高危动作的确认要求写在动作定义中,与调用方无关;不承诺项则在能力说明中列明, 不随演示环境与正式环境而改变。
端点地址、传输协议与认证形式在「AI 原生与 Agent 接入」页与本系列其余各篇保持同一口径, 本文不另行列示其他参数。
依据:该账号可见工具数以端点实时返回为准;演示环境的账号为页内公开的演示账号,用户名密码登录。
ai/tool-registry.ts(86 行)、mcp/server.ts(66 行)、tools/register.ts(30 行)、ai/tool-factory.ts(93 行)247 条动作契约、35 个业务域、该账号可见工具数由端点实时返回、写入类动作统一经过一条执行管道—— 上述各项均无需采信本文陈述,可由演示环境自行核实。