1. 为什么你的 Skill 在三个工具里要写三遍
如果你同时用 Codex 写后端、用 Cursor 改前端、用 GitHub Copilot 补测试,大概率遇到过这种糟心事:同一个「SQL 变更审查」能力,在 Codex 里是一份 SKILL.md,在 Cursor 里要重写成.cursor/rules,到了 Copilot 又得塞进.github/copilot-instructions.md。三份内容 90% 重复,改一处规则另外两份就漂移,最后没人知道哪份才是最新的。
Agent Plugins 1.0 想解决的就是这个。它把「可复用的部分」收敛成一个厂商中立的目录格式:根目录放plugin.json,工作流放进skills/,MCP Server 写进根目录mcp.json。兼容的客户端从同一份插件里发现并加载这些组件,你不用再维护三套清单。
但这里有个必须说清楚的边界:Agent Plugins 1.0 目前只标准化了两样东西——Agent Skills 和 MCP Servers。Hooks、Rules、Commands、Custom Agents、安装入口、权限和认证,仍然由各客户端自己决定。所以「写一次,到处运行」的准确含义是:工作流和工具只写一次,客户端特有能力放进独立适配层。
这篇不聊概念,直接带你从零做一个能跑的portable-sql-review插件:同一份 SQL 审查 Skill 配同一个本地 MCP Server,在 Codex、Cursor、Copilot 里复用,并给出安装、协议测试、功能验收和排错方法。适合谁?适合已经在多个 AI 编码工具之间来回切换、被配置重复折磨过的开发者。
先看一张对照表,心里有个底:
| 能力 | Codex | Cursor | Copilot | 是否属于 1.0 可移植核心 |
|---|---|---|---|---|
skills/<name>/SKILL.md | 支持 | 支持 | 支持 | 是 |
| Skill 的 scripts/references/assets | 支持 | 支持 | 支持 | 是 |
mcp.json中的 stdio MCP | 支持 | 支持 | 支持 | 是 |
| Streamable HTTP MCP | 支持 | 支持 | 支持 | 是 |
| Hooks | 各自实现 | 各自实现 | 各自实现 | 否 |
| Rules / Instructions | 各自实现 | 各自实现 | 各自实现 | 否 |
| Custom Agents / Commands | 各自实现 | 各自实现 | 各自实现 | 否 |
| 安装与 Marketplace | 各自实现 | 各自实现 | 各自实现 | 否 |
| OAuth、密钥、权限 | 各自实现 | 各自实现 | 各自实现 | 否 |
一句话概括:可移植核心只放 Skill 和 MCP,其余差异全部下沉到适配层。
2. 前置准备:TaoToken 接入与插件目录骨架
在动手写插件之前,先把模型调用这条链路打通。因为 Skill 本身只是提示词和流程,真正干活的是背后的模型和 MCP 工具。我这边统一用 TaoToken 做模型接入,它的好处是一个 Key 就能覆盖 Codex、Cursor、Copilot 这类客户端需要的 OpenAI 兼容接口,省得每个工具配一套。
你需要准备三样东西:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,Key 在控制台生成,Model ID 按你实际要用的模型填。这三件套在后面的 Codex、Cursor、Copilot 配置里都会反复出现,先记牢。
去控制台拿 Key 的入口在这里:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
拿到 Key 之后,先建插件目录骨架。最小插件只需要一个清单加一个 Skill:
portable-sql-review/ ├── plugin.json ├── skills/ │ └── sql-change-review/ │ ├── SKILL.md │ └── references/ │ └── review-rules.md ├── mcp.json ├── server/ │ └── server.mjs ├── tests/ │ └── test-server.mjs ├── samples/ │ └── risky.sql └── package.json这里三个文件千万别混淆:
- 根目录
plugin.json:声明 Agent Plugins 版本和插件身份,可移植。 skills/<name>/SKILL.md:声明任务工作流,可移植。- 根目录
mcp.json:声明 MCP Server 的启动方式,可移植。
注意plugin.json是闭合 Schema,只允许$schema、name、version、description、author、homepage、repository、license、keywords、extensions这些顶层字段。你千万别自作聪明加skills、mcpServers、hooks字段,客户端会直接报错并忽略。Skill 不需要声明路径,客户端固定扫描skills/;MCP 必须放在根目录mcp.json,不能内联。
另外提醒一句:Agent Plugins 1.0 没有统一 OAuth 和凭据保存协议,认证仍由客户端管理。所以插件清单里的env、headers都是可见配置,不要把 API Key 写进去。模型侧的 Key 交给客户端自己的配置去存。
3. 可复制配置:plugin.json、mcp.json 与 SKILL.md
这一节是全文的核心,所有片段都能直接复制。先写根清单plugin.json:
{ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "portable-sql-review", "version": "1.0.0", "description": "Review SQL changes with a portable workflow and a deterministic local MCP checker.", "author": { "name": "Example Author" }, "license": "MIT", "keywords": ["sql", "database", "review", "migration", "safety"] }再写mcp.json,注意command必须是单个可执行文件名,参数全部拆进args:
{ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", "mcpServers": { "portable-sql-review": { "type": "stdio", "command": "node", "args": ["${PLUGIN_ROOT}/server/server.mjs"], "cwd": "${PLUGIN_ROOT}" } } }${PLUGIN_ROOT}指向插件安装目录,${PLUGIN_DATA}指向客户端分配的持久化可写目录。两个 Schema 版本必须一致,都写1.0.0。
接着写 Skill 主文件skills/sql-change-review/SKILL.md:
--- name: sql-change-review description: Review SQL files, database migrations, DML, DDL, and stored-procedure changes for destructive operations, semantic drift, transaction risks, locking risks, and missing verification steps. Use when the user asks to review, migrate, execute, or troubleshoot SQL changes. --- # SQL Change Review Review the change as an engineering change, not as isolated SQL syntax. ## Workflow 1. Identify the target database, affected objects, execution environment, and intended business result. 2. Read the complete SQL change or diff. Do not infer omitted statements. 3. Call the `review_sql` tool from the `portable-sql-review` MCP server for every changed SQL script. 4. Treat tool findings as a deterministic first pass, not as proof that the SQL is safe. 5. Manually review row-selection semantics, transaction boundaries, lock duration, idempotency, and rollback plans. 6. Never execute destructive SQL merely because the static checker returned no finding. ## Required output 1. 60-second conclusion: safe, needs confirmation, or blocked. 2. Risk table: severity, location, evidence, impact, and fix. 3. Required changes before execution. 4. Verification SQL: pre-check, execution guard, and post-check. 5. Remaining uncertainty that must be confirmed by a human.这份 Skill 有四个关键点:触发条件明确(SQL、DML、DDL、迁移都能匹配)、工具调用明确(要求调用review_sql)、工具边界明确(静态扫描通过不等于可执行)、输出结构明确(三端最终都产出相似报告)。
领域规则单独放references/review-rules.md,让主文件保持短小,Agent 按需读取:
# SQL review rules ## Block by default - DELETE 或 UPDATE 缺少有效 WHERE 条件。 - TRUNCATE、DROP TABLE 没有恢复方案。 - 执行环境不明确时操作生产数据库。 - 使用不可信输入拼接动态 SQL。 ## Require evidence - 预计和实际影响行数。 - 大批量 DML 的索引支持。 - 长时间操作的锁等待检查。 - 不可逆修改的执行前快照。最后是 MCP Server 的核心扫描逻辑,用 Node.js 原生实现,不依赖第三方包:
function reviewSql(sql, dialect = "generic") { const normalized = String(sql ?? "") .replace(/--.*$/gm, " ") .replace(/\/\*[\s\S]*?\*\//g, " "); const findings = []; for (const match of normalized.matchAll(/\bDELETE\s+FROM\s+[\w."`]+[\s\S]*?(?=;|$)/gi)) { const statement = match[0]; if (!/\bWHERE\b/i.test(statement) || /\bWHERE\s+1\s*=\s*1\b/i.test(statement)) { findings.push({ severity: "critical", code: "DELETE_WITHOUT_FILTER", message: "DELETE 缺少有效 WHERE 条件,可能删除整表数据。", evidence: statement.trim() }); } } for (const match of normalized.matchAll(/\bUPDATE\s+[\w."`]+[\s\S]*?(?=;|$)/gi)) { const statement = match[0]; if (!/\bWHERE\b/i.test(statement) || /\bWHERE\s+1\s*=\s*1\b/i.test(statement)) { findings.push({ severity: "critical", code: "UPDATE_WITHOUT_FILTER", message: "UPDATE 缺少有效 WHERE 条件,可能更新整表数据。", evidence: statement.trim() }); } } if (/\bTRUNCATE\b/i.test(normalized)) { findings.push({ severity: "critical", code: "TRUNCATE", message: "检测到 TRUNCATE,必须确认环境和恢复方案。" }); } if (/\bDROP\s+TABLE\b/i.test(normalized)) { findings.push({ severity: "critical", code: "DROP_TABLE", message: "检测到 DROP TABLE,默认阻断。" }); } const blocked = findings.some(item => item.severity === "critical"); return { dialect, decision: blocked ? "blocked" : findings.length ? "review_required" : "no_static_finding", findings, disclaimer: "静态检查不替代执行计划、锁分析和业务语义核对。" }; }为什么不让 Skill 用自然语言判断全部风险?因为正则扫描是确定性的,同样的 SQL 每次得到相同结果,MCP Tool 还能独立测试。Agent 把精力留给事务、锁、字段映射和业务语义,这才是正确的分工。
4. 验证请求:协议测试与三端加载步骤
写完别急着丢给 Agent,先测 MCP 协议本身。测试脚本会做四件事:initialize握手、tools/list发现review_sql、危险 SQL 返回blocked、带限定条件的 DELETE 不被误判。
cd portable-sql-review node tests/test-server.mjs预期输出:
PASS: initialize, tools/list, risky SQL, scoped SQL这一步失败就别怀疑 Agent,问题一定在插件、Node.js 或 MCP 协议层。顺手校验 JSON 语法:
python -m json.tool plugin.json > /dev/null python -m json.tool mcp.json > /dev/null准备测试用例samples/risky.sql:
-- 预期:blocked DELETE FROM demo_orders; -- 预期:blocked UPDATE demo_order_items SET quantity = 0 WHERE 1 = 1;Cursor 加载:把插件复制到本地插件目录,然后重启或执行Developer: Reload Window。
mkdir -p ~/.cursor/plugins/local cp -R ./portable-sql-review ~/.cursor/plugins/local/打开 Customize,确认能看到sql-change-reviewSkill 和portable-sql-reviewMCP Server 已启用。
GitHub Copilot 加载:Copilot CLI 支持从本地目录安装。
copilot plugin install ./portable-sql-review copilot plugin list进入交互会话后用/skills list检查 Skill 是否被发现。
Codex 加载:先建一个最小 Marketplace,目录结构如下:
portable-demo-marketplace/ ├── .agents/plugins/marketplace.json └── plugins/portable-sql-review/marketplace.json内容:
{ "name": "portable-demo", "interface": { "displayName": "Portable Demo" }, "plugins": [ { "name": "portable-sql-review", "source": { "source": "local", "path": "./plugins/portable-sql-review" }, "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" }, "category": "Productivity" } ] }然后添加并安装:
codex plugin marketplace add /absolute/path/to/portable-demo-marketplace codex plugin add portable-sql-review@portable-demo codex plugin list --json三端装好后,用同一条验收指令测试:
请使用 sql-change-review 检查 samples/risky.sql, 给出 60 秒结论、风险表、修改建议和验证 SQL。验收时别只看 Agent 有没有说「危险」,要逐项检查:是否真的调用了 MCP Tool、是否识别两个危险语句、是否给出blocked、是否没有擅自执行 SQL、是否输出执行前后验证方案、是否说明静态扫描的能力边界。
5. 常见报错排查:401、local proxy failed 与 OAuth
跨三端跑插件,报错基本集中在几类。下面按真实错误对照排查。
401 Unauthorized:模型侧 Key 无效或没带上。检查客户端里配置的 Base URL 是否为https://taotoken.net/api,Key 是否复制完整(前后别带空格),Model ID 是否拼写正确。三件套缺一不可,尤其 Codex 的auth.json里如果只填了 Key 没填 Base URL,就会 401。
local proxy failed / connection refused:MCP Server 没起来。先手动跑node server/server.mjs看有没有报错,再确认mcp.json里command是node而不是一整段 Shell 命令,args是数组。如果 Node.js 不在客户端进程可见的 PATH 里,也会连不上,用绝对路径或确保环境变量一致。
reading choices / 返回结构解析失败:多半是模型返回格式和客户端预期不符。检查 Model ID 是否选对了兼容模型,有些客户端对choices字段结构敏感。换一个明确支持 OpenAI 兼容格式的模型通常能解决。
OAuth / 认证弹窗反复出现:Agent Plugins 1.0 没有统一认证协议,认证由客户端管理。如果插件清单里写了env或headers存密钥,删掉,改由客户端自己的凭据机制保存。Codex 的auth.json、Cursor 的设置、Copilot 的登录态各自独立,别指望一份配置通吃。
Skill 出现但 MCP 没出现:优先查mcp.json是否在插件根目录、文件名有没有误写成.mcp.json、企业管理员是否关闭了本地插件导入。
Skill 藏太深没被发现:规范只扫描skills/<skill-name>/SKILL.md这一层,不会递归。skills/database/review/xxx/SKILL.md这种结构直接失效。
插件依赖当前工作目录:安装后路径会变。访问插件内文件一律用${PLUGIN_ROOT},写缓存用${PLUGIN_DATA},别用相对路径。
一个客户端通过就以为三端都通过:标准允许客户端逐步实现组件,每个目标客户端都要跑相同验收用例并记录版本。
排错时如果卡在接入层,直接翻接入文档对照字段:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
6. 长期编码与 Agent 场景的落地建议
如果你只是偶尔审查几条 SQL,上面这套已经够用。但如果你要把这套插件长期挂在日常编码流程里,甚至让 Agent 自动跑,那模型调用的稳定性和额度就变成瓶颈。我自己的做法是把长期编码和 Agent 任务单独走 Coding Plan,避免和临时对话抢额度。
Coding Plan 入口:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
想先验证模型对话效果,可以用模型对话页快速试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
最后给一条仓库组织建议,避免版本分叉:
agent-plugin-repo/ ├── plugins/portable-sql-review/ # 唯一业务能力来源 ├── .agents/plugins/marketplace.json ├── .github/plugin/marketplace.json └── adapters/ ├── codex/ ├── cursor/ └── copilot/plugins/是唯一核心,两份 Marketplace 只是分发入口,adapters/只放无法标准化的 Hooks、Rules、Commands。绝不允许在适配层复制 SKILL.md,CI 必须验证所有适配层仍指向同一个核心版本。
还有一个容易踩的坑:别为了「统一」把 Skill 写成空泛流程。跨端通用的 Skill 应该避免依赖某个客户端独有的工具名、专属 Slash Command、隐藏目录或未标准化的 Hook 返回格式。正确做法是在 Skill 里引用逻辑工具名和目标能力,在 MCP 里提供确定性工具,把厂商特有增强放进适配层,再用同一套验收用例防止行为漂移。
Hooks 为什么不能一起带走?因为各客户端的生命周期事件、输入输出 JSON、阻断语义、权限模型、脚本环境都不同。如果 SQL 审查还想在命令执行前强制拦截,正确姿势是:公共 Skill 说明何时审查、公共 MCP 做确定性扫描、各端 Hook 按各自协议阻断、CI 作为最终门禁。Skill 负责指导,MCP 负责能力,Hook 负责节点控制,CI 负责强制。
真正能一次编写的是 Skill 工作流、引用的脚本规则资源、MCP Server 及其标准启动配置。仍然要分别处理的是 Marketplace 安装入口、Hooks/Rules/Commands、权限认证密钥、UI 与企业策略。用 Agent Plugins 1.0 保存唯一的可移植核心,用很薄的适配层处理剩余差异,再用一套相同验收用例守住行为一致性——这才是「写一次,多端生效」的稳妥落地方式。