第 36 章:开发完整插件

源码验证日期:2026-05-15,基于 commit
0d81bb6
前面几章,我们学了工具的输入验证、权限规则、MCP 服务器接入和多 Agent 协作。这些是”零件”。现在,是时候把这些零件组装成一个完整的产品了。
Claude Code 的插件系统是零件的容器。一个插件可以包含:
- Skills(技能)——可被 AI 调用的命令
- Agents(代理)——特定类型的工作单元
- Hooks(钩子)——在特定事件发生时执行的脚本
- MCP Servers——外部工具服务器
- Commands(命令)——用户可用的斜杠命令
这一章,我们从零开始开发一个完整的插件,覆盖从目录结构到验证分发的全流程。
路线图
1 | graph LR |
插件的结构全景
一个标准的插件目录看起来像这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14db-assistant/
├── .claude-plugin/
│ └── plugin.json # 插件 manifest(身份证)
├── skills/ # 技能定义
│ ├── query-database/
│ │ └── SKILL.md # 技能的 Markdown 文件
│ └── migrate-schema/
│ └── SKILL.md
├── agents/ # Agent 定义
│ └── db-inspector.md # Agent 的 Markdown 文件
├── commands/ # 斜杠命令
│ └── db-status.md # 命令的 Markdown 文件
└── hooks/
└── hooks.json # 钩子配置
注意几个关键点:
plugin.json必须放在.claude-plugin/目录下——这是插件系统查找 manifest 的标准位置- Skills 必须是
skills/<name>/SKILL.md的格式——每个技能是一个目录,目录下有SKILL.md文件 - Agents 和 Commands 是平铺的
.md文件——不需要子目录
编写 plugin.json manifest
plugin.json 是插件的身份证:1
2
3
4
5
6
7
8
9
10
11
12{
"name": "db-assistant",
"version": "1.0.0",
"description": "Database query and management tools for Claude Code",
"author": {
"name": "Your Name",
"email": "[email protected]"
},
"skills": ["skills/query-database", "skills/migrate-schema"],
"agents": ["agents/db-inspector"],
"hooks": "hooks/hooks.json"
}
源码中 manifest 的 schema 定义在 src/utils/plugins/schemas.ts:1
2
3
4
5
6
7
8
9
10
11
12// 文件:src/utils/plugins/schemas.ts
export const PluginManifestSchema = lazySchema(() =>
z.object({
...PluginManifestMetadataSchema().shape, // name, version, description, author
...PluginManifestHooksSchema().partial().shape, // hooks 路径
...PluginManifestCommandsSchema().partial().shape, // commands 路径
...PluginManifestAgentsSchema().partial().shape, // agents 路径
...PluginManifestSkillsSchema().partial().shape, // skills 路径
...PluginManifestMcpServerSchema().partial().shape, // MCP 服务器配置
...PluginManifestSettingsSchema().partial().shape, // 插件设置
}),
)
每个子 schema 都是 .partial() 的——意味着 manifest 中所有的组件都是可选的。一个插件可以只提供 skills,也可以同时提供 skills、agents、hooks 和 MCP 服务器。
name 字段的命名规则:推荐使用 kebab-case(小写字母、数字、连字符),比如 db-assistant。
创建 Skill
Skill 是 AI 可以调用的能力,定义在 Markdown 文件中。每个 Skill 由 YAML frontmatter(元数据)和 Markdown 正文(提示词)组成。
创建 skills/query-database/SKILL.md:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19---
description: "Execute a SQL query against the configured database and return results"
allowed-tools:
- Bash
- Read
---
You are a database query expert. When the user asks you to query data:
1. Analyze the user's request to determine what data they need
2. Construct a safe SQL query (only SELECT statements)
3. Use the Bash tool to execute: `psql $DATABASE_URL -c "<query>" -t -A`
4. Format the results in a readable table
5. If the query fails, explain the error and suggest a fix
Important rules:
- NEVER execute DROP, DELETE, or TRUNCATE statements
- Always use parameterized queries when possible
- Limit results to 100 rows unless the user specifies otherwise
frontmatter 中的字段:
description——告诉 AI 什么时候应该使用这个 skillallowed-tools——这个 skill 可以使用哪些工具
Markdown 正文是给 AI 的系统提示词——当 skill 被调用时,这段文字会被注入到 AI 的上下文中。
Skill 的四大来源
| 来源 | 加载方式 | 典型场景 |
|---|---|---|
| Bundled(内建) | 编译时打包进二进制 | 核心命令如 /help、/init |
| skills/ 目录 | 扫描项目或全局配置的 skills/ 目录 | 项目级自定义提示词 |
| MCP(远程) | 通过 MCP 服务器动态注册 | 第三方工具提供的命令 |
| Plugin(插件) | 从插件的 skills/ 目录加载 | 插件分发的可复用技能 |
加载顺序是 bundled > skills/ > MCP > plugin,同名 skill 后加载的不会覆盖先加载的。
创建 Agent
创建 agents/db-inspector.md:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23---
description: "Inspect database schema, indexes, and performance"
tools:
- Bash
- Read
- Grep
max_turns: 15
---
You are a database inspector agent. Your job is to analyze database schema and performance.
Available commands:
- `psql $DATABASE_URL -c "\dt"` — list all tables
- `psql $DATABASE_URL -c "\d table_name"` — describe a table
- `psql $DATABASE_URL -c "\di"` — list all indexes
When asked to inspect a database:
1. Start by listing all tables
2. Describe the relevant tables
3. Check indexes on frequently queried columns
4. Report findings in a structured format
Keep your analysis under 500 words.
Agent 的 frontmatter 字段和上一章看到的 AgentDefinition 是对应的:tools、max_turns、description。
创建 Hook
Hooks 是在特定事件发生时自动执行的脚本。创建 hooks/hooks.json:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 validate_sql.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 log_query.py"
}
]
}
]
}
这个 hooks 配置做了两件事:
- PreToolUse——在每次调用 Bash 工具之前,验证命令安全性
- PostToolUse——在每次调用 Bash 工具之后,记录操作日志
HookCommand 的四种类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
command | 执行 Shell 命令 | 运行脚本、调用 CLI 工具 |
prompt | 注入一段 AI 提示词 | 修改 AI 行为,注入上下文 |
agent | 启动一个子 Agent | 需要独立工具集的复杂处理 |
http | 发送 HTTP 请求 | 调用远程 API、通知外部系统 |
command 类型最常用。prompt 类型不执行代码,而是在 AI 上下文中注入额外指令。agent 类型最重,会启动一个完整的子 Agent。http 类型适合和外部系统集成。
inline 与 fork 执行模式
| 模式 | 工作方式 | 适用场景 |
|---|---|---|
| inline | 在主进程中同步执行 | 需要结果才能继续(如安全检查) |
| fork | fork 子进程执行 | 发后即忘的操作(如日志记录) |
选择原则:需要结果 -> inline,只需执行 -> fork。
HookEvent 完整列表
Claude Code 定义了 28 种 HookEvent,覆盖了会话、工具、权限、协作等各个阶段:
工具相关:PreToolUse、PostToolUse、PostToolUseFailure
权限相关:PermissionDenied、PermissionRequest
会话相关:SessionStart、SessionEnd、Stop、StopFailure
子 Agent 事件:SubagentStart、SubagentStop
压缩相关:PreCompact、PostCompact
协作相关:TeammateIdle、TaskCreated、TaskCompleted
交互相关:Elicitation、ElicitationResult、UserPromptSubmit
环境相关:ConfigChange、WorktreeCreate、WorktreeRemove、InstructionsLoaded、CwdChanged、FileChanged、Notification
这些事件在 src/schemas/hooks.ts 中通过 HookEvent 类型定义。绝大多数插件只需要 PreToolUse 和 PostToolUse。
定义 MCP 服务器
插件还可以自带 MCP 服务器。在 plugin.json 中添加:1
2
3
4
5
6
7
8
9
10
11
12
13{
"name": "db-assistant",
"version": "1.0.0",
"mcpServers": {
"db-query": {
"command": "node",
"args": ["./mcp-servers/db-query-server.js"],
"env": {
"DATABASE_URL": "${DATABASE_URL}"
}
}
}
}
插件 MCP 服务器的名字会被加上 plugin: 前缀。比如上面的 db-query 会变成 plugin:db-assistant:db-query。
插件 MCP 服务器还有去重机制——如果插件提供的服务器和用户手动配置的服务器实际上是同一个(相同的命令或 URL),插件版本会被抑制。手动配置总是优先于插件配置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 文件:src/services/mcp/config.ts
export function dedupPluginMcpServers(pluginServers, manualServers) {
const manualSigs = new Map()
for (const [name, config] of Object.entries(manualServers)) {
const sig = getMcpServerSignature(config)
if (sig) manualSigs.set(sig, name)
}
const servers = {}
for (const [name, config] of Object.entries(pluginServers)) {
const sig = getMcpServerSignature(config)
if (manualSigs.has(sig)) continue // 手动配置优先
servers[name] = config
}
return { servers }
}
插件的生命周期
一个插件从创建到运行,经历以下阶段:
加载(Loading)。loadAllPluginsCacheOnly() 读取所有启用插件的 manifest,验证结构,收集组件路径:1
2
3
4
5
6
7
8
9// 文件:src/utils/plugins/pluginLoader.ts
export const loadAllPluginsCacheOnly = memoize(async (): Promise<PluginLoadResult> => {
const result: PluginLoadResult = { enabled: [], disabled: [], errors: [] }
// 加载 marketplace 插件
// 加载内建插件
// 加载 session 插件
// 验证每个插件的 manifest
return result
})
注册(Registration)。加载后,每个组件被注册到对应的系统:Skills 注册到命令系统,Agents 注册到 AgentTool 的可用列表,Hooks 注册到 hook 执行器,MCP 服务器注册到连接管理器。
运行(Runtime)。注册完成后,插件在后台工作。AI 可以调用它的 skills,系统会执行它的 hooks,MCP 服务器提供额外的工具。
热重载(Hot Reload)。当远程策略设置变更时,插件系统会检测到变化并重新加载:1
2
3
4
5
6
7
8
9
10
11
12// 文件:src/utils/plugins/loadPluginHooks.ts
export function setupPluginHookHotReload(): void {
settingsChangeDetector.subscribe(source => {
if (source === 'policySettings') {
const newSnapshot = getPluginAffectingSettingsSnapshot()
if (newSnapshot === lastPluginSettingsSnapshot) return
clearPluginCache(...)
clearPluginHookCache()
void loadPluginHooks()
}
})
}
验证插件
Claude Code 提供了内置的验证工具:1
claude plugin validate .
验证器会检查:
- JSON 语法——
plugin.json是否是合法的 JSON - Schema 合规——是否符合
PluginManifestSchema的要求 - 路径安全——组件路径是否包含
..(路径穿越攻击) - 命名规范——
name是否是 kebab-case - 组件完整性——引用的 skill/agent/command 文件是否存在
- hooks 格式——
hooks.json是否符合PluginHooksSchema
验证逻辑在 src/utils/plugins/validatePlugin.ts 中。通过验证的插件应该零错误。
打包一个完整示例
让我们把所有组件打包成最终的插件。回顾一下完整的目录结构:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16db-assistant/
├── .claude-plugin/
│ └── plugin.json
├── skills/
│ ├── query-database/
│ │ └── SKILL.md
│ └── migrate-schema/
│ └── SKILL.md
├── agents/
│ └── db-inspector.md
├── commands/
│ └── db-status.md
├── hooks/
│ └── hooks.json
└── mcp-servers/
└── db-query-server.js
这个插件提供了:
- 两个 skill:
query-database(查询数据库)和migrate-schema(执行迁移) - 一个 agent:
db-inspector(检查数据库结构) - 一个 command:
/db-status(快速查看数据库状态) - 一组 hooks:PreToolUse 验证 SQL 安全性,PostToolUse 记录查询日志
- 一个 MCP 服务器:
db-query(提供程序化的查询工具)
插件设计四原则
| 原则 | 说明 | 实践方式 |
|---|---|---|
| 单一职责 | 每个插件只做一件事 | DB 插件专注 DB 操作,不要混入 Git 功能 |
| 幂等性 | 多次安装结果一致 | 用 settings.json 记录状态,不依赖文件系统快照 |
| 错误隔离 | 插件崩溃不影响主程序 | 所有 hook 执行用 try/catch 包装 |
| 版本兼容 | 考虑主程序版本升级 | 在 plugin.json 中指定兼容版本范围 |
错误隔离的具体实现:源码中插件 hook 的执行都经过 try/catch 包装。如果 hook 脚本抛出异常,系统捕获错误、记录日志,然后继续执行下一个 hook。
分发插件——Marketplace
插件通过 marketplace 分发。一个 marketplace 是一个 Git 仓库,包含一个 marketplace.json 文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"metadata": {
"name": "my-marketplace",
"description": "My collection of Claude Code plugins"
},
"plugins": [
{
"name": "db-assistant",
"source": "./plugins/db-assistant",
"version": "1.0.0",
"description": "Database query and management tools"
}
]
}
用户通过以下命令安装:1
claude plugin add db-assistant@my-marketplace
插件系统会克隆或拉取 marketplace 仓库,找到插件的源代码目录,验证 manifest,安装到本地缓存,注册到用户的 enabledPlugins 设置中。
常见错误
| 常见错误 | 检查方法 |
|---|---|
| skills 不显示 | skills 必须是 skills/<name>/SKILL.md 格式,子目录下必须有 SKILL.md |
| hooks.json 解析失败导致整个插件无法加载 | 运行 claude plugin validate . 检查格式 |
| 插件 MCP 服务器和手动配置冲突 | 手动配置优先。用 /mcp 查看哪个版本生效了 |
| 企业环境下插件被阻止 | 联系管理员,把你的 marketplace 添加到允许列表 |
| 组件路径报错 | 不要用 ..,所有路径相对于插件根目录 |
试试看
- 创建一个最小插件。只包含一个 skill 的插件——一个”代码格式化”skill。先运行
claude plugin validate .,确认通过。然后安装并让 AI 使用它。 - 给 db-assistant 插件添加一个 PostToolUse hook。当 AI 执行完 Bash 命令后,用
http类型发送一条通知到 webhook。 - 创建一个包含自定义 Agent 的插件。Agent 只能使用 Read 和 Grep 工具,负责”阅读理解”——给定一个文件路径,Agent 阅读文件并生成摘要。
检查点
- 目录结构——
.claude-plugin/plugin.json是 manifest,skills/、agents/、commands/、hooks/是组件 - plugin.json——插件的身份证,声明包含哪些组件,所有字段都是可选的
- Skills——Markdown 文件,frontmatter 是元数据,正文是 AI 提示词,必须放在
skills/<name>/SKILL.md - Agents——独立的对话实体,有工具集和轮数限制
- Hooks——事件驱动的自动化脚本,支持四种类型和 28 种事件
- MCP Servers——插件自带的外部工具服务器,自动去重,手动配置优先
- 生命周期——加载 -> 注册 -> 运行 -> 热重载
- 验证——
claude plugin validate检查完整性和安全性 - 分发——通过 marketplace 仓库分发插件
插件系统的设计哲学是组合优于继承。每个插件是一个独立的模块,可以只包含一个 skill,也可以同时包含 skills、agents、hooks 和 MCP 服务器。从小处着手,逐步扩展。