← AI 原理与应用设计 · 第 5 篇

连接工具与 Agent 时,先定义谁负责执行

区分 MCP 工具、A2A 任务、SDK 执行循环与 skills 材料,以窄读取契约检查授权、参数和结果责任。

学完这一篇,你能做到

为 Agent 连接画出执行责任,验证工具白名单、资源范围与超时后的状态处理。

资料助手能读取笔记,现在希望接入外部工具与另一名整理 Agent。连通协议解决消息怎样交换,业务仍要决定谁能执行、可操作哪些资源、结果怎样持久化。一个工具声明存在,不能让模型自动获得使用它的权限。

案例只允许读取笔记 N-1。外部 Agent 可以建议分类与任务步骤,但不能扩大这个资源范围。本文用离线契约检查演示执行前的校验,不启动 MCP 服务、A2A 服务或任何 Agent SDK。

把一次行为拆成独立责任

用户表达目标,模型提出动作,策略层判断是否允许,执行器调用能力,观测层记录结果,状态存储保存进度。即使这些职责运行在一个进程里,也应能分别检查。否则模型的一段文字很容易越过参数与权限验证直接变成副作用。

需要持久化的记录包括任务 ID、步骤、输入版本、授权范围、结果引用与错误状态。日志可以描述执行,但不能代替权威任务状态。重启之后要知道哪些动作已经成功,哪些结果未知,哪些允许重试。

对写入和外部副作用定义幂等与确认条件。工具调用超时可能发生在操作已提交之后,不能一律重新执行。授权应绑定具体动作与资源,外部 Agent 的请求或模型自称“已获批准”都不能成为扩大权限的依据。

MCP 连接工具等能力提供者

MCP 定义客户端与服务端交换能力的协议,包括工具、资源和提示等概念。工具可以公开名称、描述与输入 schema,客户端发现后再发起调用。生命周期、能力协商与传输方式需要按对应版本实施,不能只拼一份 JSON 就宣称兼容。

schema 帮助检查形状,不证明对象属于当前用户。服务端需要验证参数与业务授权,客户端也需要控制哪些工具向模型公开。不同传输的认证安排各有条件,不能把某个 HTTP 授权机制直接用于所有本地进程连接。

官方工具规范关注调用可见性与人类控制。产品应让用户知道工具可能做什么,必要确认需要展示实际动作。只读工具仍可能泄露数据或消耗资源,所以权限检查与预算并不能省略。

A2A 的任务协作有另一组边界

A2A 描述 Agent 能力、消息与任务交互,Agent Card 可以声明服务端点、技能与认证要求。能力声明帮助发现服务,不能证明服务可信,也不能替用户授予任务权限。身份、信任、数据传输范围与结果处理需要应用自己决定。

长任务会有开始、运行、需要输入、结束等状态,具体状态与操作应核对使用版本。远程任务成功表示对方报告完成,本地仍要验证返回资产、字段与任务要求。未知结果、取消和迟到回复要有处理规则。

A2A 与 MCP 可以在同一系统里出现:一个 Agent 委派任务,同时通过 MCP 使用窄工具。它们不自动提供所有实现间的兼容性,也不能把任务协作与工具执行混称成一种协议。先确定边界,再选择需要的部分。

SDK 组织执行循环,skills 提供材料

Claude Agent SDK 将 Agent 循环、工具与上下文管理以程序化方式提供,具体能力与运行要求取决于当前文档。SDK、普通模型客户端、CLI 与托管 Agent 产品有不同执行责任,不能把名称相似当作相同权限或托管方式。

使用 SDK 时检查任务怎样开始与继续、工具如何接入、权限回调与中断怎样处理、状态保存在哪里。本文不写未经执行的 SDK 代码,也不把某个产品的权限模式推定为其他框架默认行为。

skills 通常打包可复用指令与资源,有时附带可执行脚本。它们被读取或运行,不是新学出的模型权重。引入 skill 前检查来源、脚本、网络与文件操作能力;其中的说明仍要遵守当前用户授权,不能自行扩展任务。

执行前校验窄工具契约

本例只有 read_note,必须恰好包含 note_id 字段,并属于可信程序提供的 authorized_ids。参数类型先检查,避免列表等异常值导致查表错误。授权集合不由调用者自己提交。

tools = {"read_note": {"required": {"note_id"}}}
def accept(name, arguments, authorized_ids):
    if not isinstance(name, str):
        return False
    contract = tools.get(name)
    return (contract is not None and isinstance(arguments, dict)
            and set(arguments) == contract["required"]
            and isinstance(arguments["note_id"], str)
            and arguments["note_id"] in authorized_ids)
assert accept("read_note", {"note_id": "N-1"}, {"N-1"})
assert not accept("delete_note", {"note_id": "N-1"}, {"N-1"})
assert not accept("read_note", {"note_id": "N-2"}, {"N-1"})
assert not accept("read_note", {"note_id": []}, {"N-1"})
print(accept("read_note", {"note_id": "N-1"}, {"N-1"}))

实际输出是 True。允许范围内的读取通过;未知工具、他人笔记与错误参数类型被拒绝。这个函数只决定是否接纳,没有读取笔记、验证身份或实现任何协议传输。

真实执行时还要重新获取对象状态与访问规则,处理授权撤销、资源删除和检查到使用之间的竞争。输出也需要过滤,只返回当前任务允许的字段。工具说明说“只读”是声明,仍应核查实现是否具有写入或外部通信副作用。

结果、重试与观测保持关联

为每次任务与工具调用分配稳定 ID,关联模型建议、参数验证、执行结果和持久状态。记录必要元数据与错误分类,避免普通日志包含笔记正文、凭据或外部私有内容。观测入口也需要访问限制。

区分协议错误、业务拒绝、工具失败与模型误选。协议版本不匹配需要协商或更新,权限拒绝需要停止或请求新的授权,临时失败可以有限重试,模型选错工具则需要评估任务说明与能力暴露范围。

重放任务时不得把历史授权无限沿用。确认其范围与有效条件,若参数改变则重新核验。对外部消息保留来源与任务关联,但默认把它当作数据;另一个 Agent 不能仅靠消息内容取得本地执行身份。

工具列表也可能变化。客户端缓存了旧 schema,服务端升级了必需参数时,应明确失败并重新发现能力,不能把旧字段猜成新字段。记录实际协商版本与工具定义版本,可以解释某次调用为什么被拒绝,并避免把协议差异归成模型错误。

练习:连接前完成执行边界表

写一张表,列出用户目标、可发现能力、输入 schema、资源权限、执行位置、结果状态与恢复动作。为只读与删除操作分别设计路径;删除若需要确认,应绑定具体对象和效果。

验收时,未知工具、额外参数、错类型和越权对象都被拒绝;外部 Agent 消息不能扩展权限;超时和迟到结果可以解释。说明哪些属于 MCP、A2A、SDK 或 skill 的职责,哪些属于业务程序。离线校验只证明这份窄契约,真实互操作还需固定版本并分别测试协议连接与业务行为。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。