Appearance
第 17 章 怎样接入和复用外部能力:MCP、Skill 与插件
本章要解决的问题
第 16 章解决了多个 Agent 怎样分工,但每个 Agent 仍只能使用程序内置的能力。接数据库、设计工具或公司 API 时,继续修改核心程序很快会变得难以维护。
- 外部工具怎样以统一方式接入并复用?
- 工具太多时,怎样避免一次性占满上下文?
- 扩展多到几十上百个时,重复与冲突怎样裁决、变更怎样在运行中生效?
- 团队的操作经验怎样按需加载?
- 扩展的权限、版本、失败和撤回应由谁管理?
学完本章,你将能区分 MCP、Skill 与插件,并设计一套可审查、可回滚的扩展流程。
17.1 为什么不能把所有工具都内置
先看一个只用于说明数量级的假设,它不是四个项目的实际统计。假设接入 40 个 MCP server,每个暴露 8 个工具,共 320 个;如果某组 schema 用当前模型的 tokenizer 实测平均约 500 token,全量注入会是约 16 万 token。真实成本必须对你的 schema 和模型重新测量:
text
320 × 实测平均 token 数 ≈ 工具定义占用的上下文
例:320 × 500 ≈ 16 万 token工具数量增加可能提高错选率,特别是名称相似、说明含糊或模型能力不足时;但不是“工具越多就必然按固定比例变差”。应同时测量 schema token、错选率、任务成功率和发现工具增加的轮次。
所以扩展机制要同时解决两个问题:接入(怎么让外部能力变成模型可调用的工具)和按需加载(怎么不让海量能力撑爆上下文)。三种机制给出了不同答案。
17.2 MCP 怎样让不同 Agent 复用同一工具
没有 MCP 时,N 个 Agent 客户端接 M 个外部工具,要写 N×M 遍各不相同的胶水代码;MCP 把它变成 N+M——每个客户端实现一次协议,每个工具暴露一次能力。
MCP(Model Context Protocol) 让 Agent 客户端用统一协议连接服务器,发现和调用工具、读取资源、获取提示模板,并在双方声明支持时处理更多交互。它像 HTTP 一样统一通信方式,但“同一服务器能否直接给多个客户端使用”仍取决于协议版本、传输方式、认证和双方支持的功能。
codex、grok 和 dsh 都支持 MCP 工具接入,但传输、命名、重连和审批并不完全相同。pi 核心明确不内置 MCP;需要时由扩展或外部桥接器接入。这样避免核心长期绑定协议状态,也意味着第三方实现的重连、审批和输出限制未必一致。
以 dsh 为例:一个插件实例连接一个 server,工具注册为 mcp__<server名>__<原始名>(前缀防跨 server 重名);单调用超时、断连自动重连与重试上限都有保守的默认值,具体清单见 17.6。启动失败是否让插件加载失败由 failOnStartupError 决定。
codex 还为 MCP 的资源(resources,服务器共享的数据)提供 list/templates/read 三个工具(见附录 D),并让 MCP tool call 进入审批/审查流程。启用 strict_auto_review 时不会走自动批准或已记住决定的快捷路径。具体是否审查仍由配置与运行状态决定,不能简化为“MCP 一律更严”。
MCP 的主要价值是复用和互操作,不是保证更快。 进程或网络传输通常会增加开销,但原生工具也可能受启动、I/O 或实现质量影响,不能断言它永远更快。一个 MCP server 能否被不同客户端直接复用,还取决于协议版本、transport、双方 capabilities、认证方式和客户端是否支持该功能;不满足时仍需配置或适配。
上图只画了握手、发现和调用三步。一个完整 MCP client 还要根据双方声明的 capabilities,决定是否支持下列功能:
| MCP 功能 | 作用 | 客户端必须做的检查 |
|---|---|---|
| tools | 让模型执行动作 | schema 校验、审批、超时、输出大小限制 |
| resources / templates | 读取 server 暴露的数据与参数化资源 | URI/租户鉴权、大小上限、来源标注 |
| prompts | 获取 server 提供的提示模板 | 只能作为低于 system/developer 的外部内容,不能自动升级指令权 |
| progress / cancellation / notifications | 报进度、取消连接内请求、通知资源变化 | 与本地 job/turn ID 关联;同一事件重复到达时只生效一次(幂等处理) |
| sampling / elicitation 等 server request | server 反过来请求模型采样或用户输入(若双方声明支持) | 独立权限、预算和 UI;外部 server 不能借 client 无限调用模型或骚扰用户 |
MCP 连接内通知也不等于持久任务调度。Agent 已离线或进程已退出时,server 的通知不会天然把会话唤醒;跨会话唤醒仍要单独的定时或事件服务、能在重启后保留的待处理队列,以及执行权限检查(第 4、9、14 章)。
17.3 工具太多时怎样按需加载
MCP 解决了接入,也带来一个新问题:工具太多。生产实现主要采用两种做法:
① 延迟加载 + 搜索:codex 的 tool_search 不会在最开始列出所有延迟工具的完整 schema。模型需要时先搜索,再让匹配的工具可用。相关实验也采用了相同做法:系统里只保留基础工具、工具目录和一个 discover_tools 工具,按需取回少量候选。
这样通常能减少初始 schema 的 token 和一次选错工具的机会,但工具目录、搜索请求、候选描述和新增轮次仍有成本。发现后的 schema 怎样加入后续请求、能否保留跨请求前缀缓存,取决于模型 API 和客户端实现,不能只根据 tool_search 这个名字推断。
② 分组并按会话选择:dsh 用 preset 为不同会话组合工具集;codex 用 defer_loading 标记延后注入工具 schema。
共同思想:工具也是上下文,同样受第 6 章的预算约束。不存在“少于某个固定数量就一定适合全量注入”的通用阈值:十个复杂工具的 schema 可能比几十个简单工具更贵,不同模型对相似工具的区分能力也不同。是否延迟加载应看四个实测量:工具 schema token、任务实际命中分布、错选率和发现额外增加的轮次/延迟。
发现不是“一搜必有”
工具发现要把“不知道”设计成正常结果,而不是强迫检索器从低相关候选里挑一个:
text
用户目标
→ discover(query, currentContext)
→ confident match: 暴露少量候选及来源/权限摘要
→ no-match: 明确返回“未找到合适工具”,允许改写目标、问用户或使用基础工具
→ 调用后得到新术语/资源 ID/错误
→ 必要时二次 discover,而不是永远锁死第一次候选no-match 很重要:一个“总能返回结果”的检索器会把无关工具包装成答案,模型随后可能带着真实权限执行错误动作。候选结果至少应包含 toolId + version + provider + requiredPermissions + score/reason,让 Runtime 和用户都知道“发现了谁、为什么、要什么权限”。第一次发现也不是会话级永久绑定;工具结果、用户澄清或任务阶段变化都可能带来更准确的二次查询。
发现质量同样需要评估,不能只看“搜索返回了几个”:
| 指标 | 回答什么 |
|---|---|
| 前 k 个候选命中率 | 正确工具是否进入前 k 个候选 |
| 无匹配判断准确率 | 本来没有合适工具时,系统是否克制地返回空 |
| 候选准确率 | 暴露给模型的候选中有多少真正相关 |
| 发现后的调用成功率 | 找到以后是否能用对参数并完成任务 |
| 发现成本 | 目录、查询、额外轮次带来多少 token 与延迟 |
评估集要同时放同义表达、模糊需求、相似工具、权限不足和无匹配任务。否则离线的前 k 个候选命中率很漂亮,线上仍可能在最危险的 no-match 场景乱选。
17.4 Skill:用到时再加载操作知识
MCP 扩展的是能力(能做什么),Skill 扩展的是知识(怎么做某类事)。一个 Skill 是一个打包的工作流:说明文档 + 模板 + 可执行脚本(比如"PPT 生成技能"包含:做 PPT 的流程文档、python-pptx 脚本、排版规范)。
做法很直接:先只展示简短目录,确认需要时才读取完整内容。以 codex 的 skills 实现(skills.list / skills.read 两个工具)为例:
text
启动时:上下文里只有一份"技能目录"(每个技能一行:名字+一句话描述) 成本需实测
识别到任务需要 pptx 技能:
→ skills.read 加载该技能的完整流程文档 按需
→ 按文档调用捆绑脚本干活
没用到的技能:完整正文不加载;目录项与选择逻辑仍有少量成本Skill 就是这一思想的又一例证。Skill 与 MCP 的本质区别:MCP 工具是“外部进程提供的可执行动作”,Skill 是“按需注入的指令与脚本”。Skill 的完整内容不必常驻,但技能目录、名字和摘要通常仍在稳定前缀或发现索引里;技能越多,越需要管理目录并测试检索是否准确。
那么,什么能力该做成专用工具,什么交给 Skill + 通用执行器完成?一张表说清:
| 形式 | 适合 | 不适合 |
|---|---|---|
专用工具,如 read_file、apply_patch | 高频稳定能力;参数需要强校验;副作用需要权限、审计或原子性保证 | 偶发的团队流程;只是几条现成命令的组合 |
| Skill + 通用执行器,如一份排障步骤配合 Shell/文件工具 | 流程经常变;需要组合多种能力;规则以文档维护更自然 | 需要强事务、精确权限或可靠机器解析的操作 |
Skill 教模型“怎样组合已有能力”,不应绕过现有权限检查——真正产生副作用的动作仍由专用工具或受限执行器完成。两者通常共存:稳定的基础动作做工具,团队流程做 Skill。
pi 是这条路线最清晰的样本之一:启动时只把 Skill 名称和描述放进系统提示,模型判断需要后再用 read 加载完整 SKILL.md。它遵循 Agent Skills 目录约定,同时允许扩展和 Package 携带 Skill。渐进披露节省固定上下文,但也把一个新问题暴露出来:模型必须先意识到自己需要这个 Skill。因此既要评估 Skill 被加载后的遵循率,也要单独评估该出现时是否被发现、无关任务里是否误触发。
17.5 插件与 Cordis:连 Agent 核心也可以替换
MCP 和 Skill 都假设 Agent 的核心程序固定不变。dsh 则把工具、模型服务、文件系统,甚至 agent loop 都注册为 Cordis 插件,因此这些实现都可以替换。Cordis 是一个依赖注入、插件化的前端框架,dsh 直接把它内嵌进仓库,当作 Agent 的插件底座。
一个 Cordis 插件包含什么
一个 dsh 插件就是一个 npm 包,核心契约可以归纳为四项:
| 契约 | 解决的问题 |
|---|---|
| 插件名 | 让配置和依赖能稳定引用它 |
| 依赖声明 | 装载前确认工具、文件系统等服务已经存在 |
| 配置 schema | 在生效前校验用户配置 |
| 装载函数 | 注册工具、提示词片段或请求事件处理器 |
关键设计是:每次注册都带着对应的撤销操作——插件卸载时,注册过的工具、提示词和事件处理器会随 Context 一起移除,因此可以热替换。内嵌在仓库里的 Cordis 框架核心较小,负责 Context、服务注册表、让插件依次处理或接收事件,以及插件实例的创建和销毁;具体行数会随版本变化,不应作为接口保证。
多份 YAML 配置按什么顺序生效
为什么会有多份 YAML?典型场景是:团队提供一份基线配置,项目与个人按需覆盖,临时实验再打一层补丁。dsh web 根据 YAML 清单加载插件。后出现的配置会替换前面的同名项,顺序如下:
text
profile(web) → bundles 顺序(dsh-base:模型适配/工具/持久化/沙箱/审批...)
→ profile 自己的 cordis.patch.yml
→ home 级 cordis.patch.yml
→ 命令行 --patch 覆盖
后面的 patch 可以替换前面的任意配置 —— 包括 agent loop 本身任意一层都能替换 agent loop 本身,意味着误配可以静默改变核心行为——多层配置需要配套的配置审查。
把接口、实现和调用方分开
dsh 为每项功能分别提供接口、实现(源码中称 provider)和调用方(通常是模型工具)。只依赖同一稳定接口的调用方可以在不改业务代码的情况下更换 provider。但“替换文件系统后 Bash、PTY(伪终端)、LSP(语言服务器协议)都不用改”过于绝对:它们还可能依赖本地进程、路径规则、文件监听、socket 和权限。更换 provider 后必须逐项确认接口行为与这些外部依赖,不能只看 TypeScript 类型相同。
每个工具插件注册工具时,还会同时注册自己的提示词 section(第 6 章)。工具和使用说明放在同一个 npm 包中发布,更新时不容易漏掉其中一项。
对比另外三家
- codex:有完整的插件安装与分发体系(模型甚至可以建议安装插件),但核心不可替换
- grok:有以插件市场为中心的分发机制,插件是"内容包"(工具集/skill 的集合),分发导向
- pi:扩展可以注册或替换工具、命令、事件钩子、UI 与 compaction 行为;Package 再把扩展、Skill、提示模板和主题打包分发。核心刻意很小,扩展则拥有完整宿主权限
四家其实回答的是同一个问题的不同层次:codex/grok 偏向“如何安装和分发能力”,dsh 问“Agent 还有什么不能被替换”,pi 则用“最小核心 + 任意扩展”把大量产品决策留给用户与包作者。
自建起步:实现一个最小 MCP 客户端需要哪五部分
17.2~17.5 已经分清了 MCP、Skill 和插件各自扩展什么。下面选 MCP 走一遍最小接入,不是因为它比其他机制更重要,而是它有清楚的跨进程协议,最适合展示“连上”和“可靠运行”之间还差什么。本节先连上,紧接着的 17.6 再补权限、超时、重连和输出限制。
MCP 的 stdio 传输使用换行分隔的 JSON-RPC 消息。最小客户端代码不长,但协议边界不能省,至少要处理五部分:
① 启动顺序:先握手,再列工具。initialize(协议版本 + 能力协商)→ 发 notifications/initialized 通知 → 之后才能 tools/list。这是协议规定的初始化顺序;不要依赖某个宽松 server 恰好接受越序请求。
② 请求-响应配对:按 id 分发。 每个请求带 id,响应按 id 找回等待中的 Promise。没有 id 的是 notification;server→client request 同样带 id,客户端必须按 capabilities 处理并回 response,不支持的方法也要返回明确的 JSON-RPC 错误,不能静默忽略让 server 永久等待。注意调用方拿到的是完整消息,还要分辨 result/error。
③ 行缓冲解析,解析失败要隔离。 子进程 stdout 按字节到达,先攒进缓冲区、按换行切分再 JSON.parse。合法日志必须写 stderr,stdout 的坏 JSON 是协议错误;记录原始行的有界预览,让该 server 的未完成请求失败并关闭或隔离这条连接,不能悄悄跳过后继续等待一个已经丢失的响应。其他 server 与内置工具是否继续工作,由上层的故障隔离设计保证。
④ 工具合并:命名空间前缀。tools/list 拿到的 schema 以 mcp__<server名>__<原始名> 合并进注册表(前缀防跨 server 重名);执行时按前缀剥开、分发回对应 server 的 tools/call。
⑤ 连接与子进程清理:退出时关闭 transport。 使用 stdio 启动本地 server 时还要终止并回收子进程,否则调用方退出后它仍会占用资源;使用 HTTP 等远程传输时则关闭连接和未完成请求,不能假设客户端有权终止远端服务。第 8 章处理后台命令时也有相同的子进程回收问题。
跑起来后,你的 Agent 就能在运行时发现并注册外部已经提供的工具。它扩大了当次可见的工具集,并不等于 Agent 凭空创造了一个可信的新能力。工具太多时,可以按 17.3 的做法把 listTools 结果存进可搜索的目录,并允许返回 no-match。
相对真实客户端,这个最小实现还省了四类工作:每次调用的超时(否则 server 卡死会挂起调用方)、断线重连、协议定义的取消通知(notifications/cancelled),以及双方 capabilities 声明过的 server→client 请求。17.6 会继续补上运行和安全检查。
17.6 接入 MCP 后怎样限制权限、恢复进程并控制成本
接入了 MCP 只完成了一半。MCP server 可能由第三方、公司内部团队或自己维护,可能是本地子进程,也可能是远程服务;无论来源,只要跨进程、网络、账号或租户边界,就要把它当成独立的信任边界。它可能崩、可能慢,输入和输出也可能被恶意构造。17.2 讲了协议,这一节回答三个具体问题:调用前检查哪些权限、server 怎样启动和重连、调用时间与输出大小怎样限制。
信任:按实际能力和来源判断,不要只看标签
MCP 工具不必然比内置工具危险,内置工具也不必然可信。安全等级应由代码来源、部署位置、数据范围、网络和文件权限、副作用及认证方式决定。生产实现提供了这些可参考的检查点:
- codex:MCP tool call 会进入 MCP 调用审查逻辑;配置
strict_auto_review时,不会采用自动批准或记住上次决定的快捷路径。最终是否询问、拒绝或允许仍取决于配置、审查结果和运行状态,不能概括为“MCP 永远比内置工具更严” - dsh:MCP 工具与其他工具共用注册和执行流程,因此可以在公共入口增加审批、输出限制等检查;默认配置并不等于自动审批。沙箱包住的是工具调用的 shell,不是 server 子进程本身;后者要单独限制启动参数、环境、网络和生命周期
- pi:核心没有 MCP 权限模型;扩展本身可执行任意宿主代码,也能新增不经过内置工具路径的能力。项目可信检查会阻止未信任项目自动加载项目扩展和包,但用户安装的扩展仍要按代码依赖审计、锁版本并放进合适的宿主沙箱
- 加载期校验:dsh 在加载时校验 serverName 唯一性(两个 server 同名,
mcp__名__工具的寻址就乱了)——把命名冲突消灭在启动时,而不是运行时报"调用歧义"
server 启动失败、崩溃或断连时怎样处理
每个 MCP server 对客户端而言是独立连接:stdio 通常对应本地子进程,HTTP 等传输可以连接远程服务。启动慢、中途崩溃、断连都需要处理。dsh 的处理清单可直接作为需求列表:
text
· 单调用超时(默认 60s)——外部进程不能拖死 Agent 的 turn
· 断连自动重连(指数退避)——重连期间相关工具调用返回错误,走 4.4.5 的错误回传
· failOnStartupError 选项——启动失败的 server 是静默跳过还是硬失败,按场景选
· codex 进一步:"多会话对账启动"("Reuse pending MCP startups during reconciliation")——
多个会话同时启动同一个 server 时等待同一次启动,不要重复创建进程设计目标是:一个 server 的故障尽量只影响它自己的工具。协议本身不会自动保证这一点:客户端要为每个 server 分开保存连接、未完成请求、工具注册和重连状态,并在连接失败时让这些请求明确结束。failOnStartupError 也说明有些产品会有意选择“关键 server 启动失败就让整个启动失败”,而不是一律忽略。
外部调用要同时限制时间、上下文和输出
- 调用超时(上面已列)
- 上下文预算:每个 MCP server 可能带来一叠工具 schema——这正是 17.3 所说的工具过多问题。客户端若把 MCP 工具纳入可检索目录,可以采用相同的延迟加载思路;是否支持、怎样加入后续请求取决于客户端与模型 API
- 输出大小:MCP 工具的返回值也要过第 8 章的截断或限量另存处理。外部输出大小不可预期,必须先按字节、条目数和内容类型限制,再决定给模型预览、分页还是文件引用
MCP 接入前检查清单
□ 工具命名冲突校验(serverName 唯一) □ 单调用超时 + 全局并发上限 □ 输出尺寸上限(截断或溢写) □ 启动失败策略(跳过 or 硬失败) □ 按工具的来源、权限和副作用决定审批/审查规则 □ 断连重连期间的错误回传文案(告诉模型"该 server 暂不可用",而不是裸的连接错误)
17.7 扩展多到成为系统:编排、冲突与热插拔
17.3 解决了"工具太多怎么加载";17.6 解决了"单个 server 怎么管"。但当 MCP server 和 Skill 多到几十上百个,会出现三个更上层的问题:同一能力来自多个来源时听谁的、发现层怎样充当调度器、变更怎样在运行中生效。
重复与冲突:前缀只解决重名,不解决重复
mcp__server__tool 命名空间保证不重名,却挡不住能力重复:三个 server 都提供"搜索文档"、两个 Skill 都教"怎么发版",模型看到的是几个功能等价的入口,选哪个全凭运气。规模化要有明确的裁决规则:
- 同能力归一:等价工具在发现层归并成一个入口,路由到默认来源;该来源不可用时再降级到备选——与 17.6"一个 server 的故障只影响它自己的工具"配套
- 按范围裁剪可见集:项目/租户/会话只看到自己需要的扩展(dsh 的 preset 就是这个方向)
- 冲突在启动期暴露:同名 server、高度相似的能力,加载时就报出来,而不是留到运行时让模型随机选(dsh 加载期校验 serverName 唯一性就是这个思路)
发现层是扩展体系的调度器
规模化之后,"接入了什么"和"本次可见什么"必须分成两层:接入层可以很大,发现层只对当前任务负责。17.3 的目录 + 检索 + no-match,在这里从"省 token 的手段"升级为编排的核心——它决定模型每次请求看到哪些工具与技能目录项,同样重要的是看不到哪些。跨会话还要考虑版本:同一个 server 在不同会话可能挂着不同版本,历史会话重放要按当次版本解释,不能假设"工具行为永远一致"(第 14 章的会话事实源)。
热插拔:变更在运行中生效,而不是重启
扩展不会只在启动时变化:用户中途安装插件、server 崩溃重连、Skill 升版本、17.8 的撤回流程,都可能发生在会话进行中。热插拔要回答四个问题:
- 变更何时对模型可见? 工具清单是上下文的一部分,中途改动会改变请求前缀、打破前缀缓存(第 2 章)。通常取"下一轮模型调用生效",当前 turn 保持稳定;小变更攒批生效,别让每次挂载都牺牲一次缓存。
- 卸载时在途调用怎么办? 要么等它完成再摘除,要么随卸载中止并把错误作为工具结果回传给模型——不能留下"模型发起了调用、结果永远不回来"的悬空。
- 重挂载要重新过权限。 同名工具卸载再装回,不能继承上次的审批记忆——版本可能已经变了。这正是 17.8 撤回流程里"MCP server 的 schema 或权限变化时重新审查"的运行时版本。
- 历史与重放不能坏。 会话日志里引用过已卸载的工具,重放时按当次事实解释(第 14 章),而不是假装它从未存在。
dsh 的 Cordis 把"每次注册都带撤销"做成了插件系统的底层保证(17.5):工具、提示词 section、事件处理器随插件一起移除。这是热插拔的正确起点——每个注册必须有对应的撤销路径,否则热插拔得进、拔不干净,残留的注册会让系统状态逐渐失真。
17.8 把一次经验变成可复用功能
17.7 解决的是“已经存在的扩展多了以后怎样编排和热更新”。最后还要回答扩展的来源问题:一次任务中的成功做法,怎样才有资格进入这套扩展系统。Agent 临时写出一段脚本、摸索出一套步骤,不代表系统应该立即把它安装成永久工具。能生成代码只能说明“值得继续验证”,不能证明它可靠。 一次偶然成功可能依赖当前目录、隐藏环境变量、过期 API,甚至只是碰巧绕过了验证。生产系统需要一套明确流程:
text
一次任务中有效的做法
→ 保存证据(任务记录/输入/输出/副作用/用户修正)
→ 判断应保存为记忆、Skill、脚本还是工具
→ 在固定起始环境中重跑并评估(正例、反例、no-match、权限失败)
→ 确定适用范围、负责人、版本和权限清单
→ 人工确认是否启用
→ 小范围试用并监控
→ 撤回/回滚先判断应该保存成什么
| 形式 | 适合保存什么 | 必须额外回答 |
|---|---|---|
| 记忆 | 用户偏好、项目事实、一次决策及来源 | 来源是否可信?何时过期?谁可读/删? |
| Skill | 可解释的多步流程、模板、使用边界 | 哪些任务触发?何时明确不该用?依赖什么脚本? |
| 脚本 | 确定、可测试、当前工具能完成的重复操作 | 输入和输出是否明确?换一台机器是否仍能运行?失败后能否恢复? |
| 工具 | 需要稳定 schema、独立权限或外部服务的动作 | 谁维护 provider?幂等/审批/审计/限流怎么做? |
不要把所有经验都做成工具。事实写进工具会迅速过期;一套说明性的流程做成 MCP server 又会增加部署和权限范围;自然语言难以稳定完成、但规则确定的转换,才值得写成脚本。新增的可执行动作越多,需要管理的权限和风险也越多。
记录“为什么值得复用”
不能只记一句“这段脚本挺好用”。最少要保存:记录 ID 与形式(memory / skill / script / tool)、成功和失败任务的引用与结果、准备在哪个范围使用(session / project / user / organization)、输入输出约定、可能产生的副作用、所需权限、源码或提示词哈希,以及负责人。
至少要保存成功与失败的任务记录、用户改过什么、真实副作用和源码/提示词哈希。没有失败样本时,最容易误以为这套做法“什么都能做”。记忆还要带来源与有效期;Skill 要写明哪些情况不该触发;脚本和工具要带可执行测试与权限清单。
重跑测试、确定版本,再由人启用
启用前要从同一份干净的起始目录、git commit 或容器镜像开始重跑,至少覆盖:
- 代表性正例:是否稳定完成目标,而非只在原来的那次任务中成功;
- 边界和反例:输入缺失、格式变化、重复调用、部分失败;
- 发现测试:该出现时能被检索到,无关任务和 no-match 时不出现;
- 权限测试:读写路径、网络域名、凭证、审批是否都在声明范围内;
- 成本测试:是否真的比现场重新探索更省,有没有把大输出永久塞进上下文。
如果候选能力来自网络搜索、第三方包或 Agent 临时生成的代码,测试前先过依赖供应链检查:
- 固定包名、下载来源、版本、lockfile 和完整性哈希,不能把搜索结果直接永久安装;
- 安装脚本默认不运行,确需运行时放进无生产凭证的隔离环境;
- 检查已知漏洞、许可证、恶意包和意外写入的 secret;
- 生成物先进入候选目录,不能直接改全局 Skill、系统提示词或所有用户可见的工具目录;
- MCP server 的工具名、描述、schema 或权限发生变化时重新审查。版本没变但哈希变了,也应拒绝加载。
通过后再确定适用范围。默认只在当前 session 使用;项目内复用要绑定仓库与版本;组织级发布需要负责人、语义版本、变更记录、兼容策略和撤回开关。Agent 可以提出建议、生成待审核的功能包和测试,但扩大到项目、用户或组织范围必须由人确认——既确认内容正确,也确认这些权限值得长期授予。
创建者权限不能自动传给产物
Agent 用全权限会话生成的脚本或工具,默认仍只在当前 session 的受限沙箱中试运行。它不能因为“由已授权 Agent 创建”就继承 Agent 的全部文件、网络、凭证和审批豁免。发布时重新声明最小能力,例如只读某个目录、只访问一个域名、凭证由专门服务按每次调用短时下发。创建权限与运行权限是两次独立决策。
上线后仍要能发现它变坏了
持续进化要拆开两种能力:系统能否提出有价值的更新,以及后续 Agent 能否发现、加载并正确使用更新。一个 Skill 写得正确但从未被触发,和一个被频繁触发却写错的 Skill,端到端分数都可能表现为“没有提升”,根因完全不同。因此至少分别记录候选提案的独立验证通过率、正确场景的激活率、激活后的遵循成功率,以及未参与提炼的保留任务集增益。
正式启用不是终点。按 capabilityId + version 记录发现次数、选择率、调用成功率、用户改写/拒绝率、权限扩大次数、成本和 p95(95 分位)延迟;新版本先小范围试用。遇到以下情况就应停止试用新版本、切回旧版或撤回:
- no-match 任务里频繁被错误召回;
- 成功率下降或用户总要重写参数;
- 请求的权限不断扩大;
- 依赖的 API/schema 已变,重跑测试不再通过;
- 新版本成本显著增加但任务质量没有提升。
回滚要覆盖四种对象:删除或失效记忆、停用 Skill 版本、撤下脚本、从工具目录注销 provider。历史任务记录仍保留原 version/hash,保证旧会话可审计,但新任务不再发现已撤回能力。
【对比小结:MCP vs Skill vs 插件】
| 机制 | 解决什么 | 扩展对象 | 粒度 | 上下文成本 | 代价 |
|---|---|---|---|---|---|
| MCP | 让客户端复用服务器提供的能力 | 工具、资源、提示模板等 | 单项能力或 server | 工具 schema 等协议数据;过多时考虑按需加载 | 协议实现、本地进程或远程服务的信任边界 |
| Skill | 工作流知识注入 | 知识(流程+脚本) | 技能包 | 完整正文按需;目录/摘要常驻 | 需要发现、版本与匹配评估 |
| 插件 | 可分发、可组合的扩展 | 一切(工具/循环/界面) | 打包集合 | 视插件而定 | 版本、权限与安全检查 |
| Cordis(dsh) | 核心本身可替换 | 一切,含 agent loop | npm 包+YAML | 同上 | 学习曲线;需要接受核心可替换的架构 |
| Extension/Package(pi) | 最小核心外组合产品能力 | 工具、事件、UI、Skill、提示和主题 | TS 扩展或可分发包 | 视启用内容而定 | 扩展有完整宿主权限;一致性由包作者负责 |
怎样选择:接入现成的外部能力 → MCP;保存团队流程 → Skill;允许用户安装较大的功能包 → 插件或 Package;如果连 agent loop、文件系统等内部实现都需要替换,可以参考 dsh 的 Cordis;如果希望核心只保留少量工具和循环钩子,可以参考 pi 的扩展边界。
扩展体系补齐后,Agent 能做的事更多,也同时引入了网络、第三方进程、版本和权限故障。下一章先处理外部信息怎样安全进入上下文;第 19 章再处理这些故障,并补上长任务、完成验证与成本控制。