技能

Layer 5: Skills, Plugins & MCP Ecosystem

Claude Code 源码深度拆解 — 基于 @anthropic-ai/claude-code npm 包 sourcesContent 还原 分析范围: SkillTool (500行) + loadSkillsDir (750行) + pluginLoader (2700行) + MCP client (3000行) + 20 个卫星模块

状态
已收录
语言
中文
来源
Projects/claude-code-deep-dive/layer5-skills-plugins-mcp.html
重复副本
0

提取结果

提示词片段

┌─────────────────────────────────────────────────────────────────┐ │ SkillTool │ │ 模型调用入口 ── validateInput() → checkPermissions() → call() │ └────────┬──────────────────────┬──────────────────────┬───────────┘ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ Skills │ │ Plugins │ │ MCP │ │ │ │ │ │ │ │ .md文件 │ │plugin.json │ client │ │ 前置声明│ │ 2700行 │ │ 3000行 │ │ 目录格式│ │ 管理器 │ │ 4种传输 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────┐ │ Command 统一类型 (types/command.ts) │ │ PromptCommand | LocalCommand | LocalJSXCommand │ └──────────────────────────┬──────────────────────────────┘ │ ┌──────────▼──────────┐ │ commands.ts │ │ 7 层优先级加载 │ │ 去重 / 过滤 / 注入 │ └─────────────────────┘
export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
executeShellCommandsInPrompt()
executeShellCommandsInPrompt
processPromptSlashCommand()
cmd.type === 'prompt' && cmd.loadedFrom === 'mcp'
type=prompt, !disableModelInvocation, source!='builtin'
┌───────────────────────────────┐ │ loadAllCommands() 7层加载 │ │ │ │ 1. bundledSkills │ registerBundledSkill() │ 2. builtinPluginSkills │ plugins/bundled/ │ 3. skillDirCommands │ .claude/skills/ (5 sources) │ 4. workflowCommands │ WORKFLOW_SCRIPTS gate │ 5. pluginCommands │ plugin commands/ dir │ 6. pluginSkills │ plugin skills/ dir │ 7. COMMANDS() │ 80+ built-in commands └──────────────┬────────────────┘ │ ┌──────────────▼────────────────┐ │ getCommands() 运行时过滤 │ │ │ │ + meetsAvailabilityRequirement│ │ + isCommandEnabled() │ │ + getDynamicSkills() inject │ │ + MCP skills (via AppState) │ └──────────────┬────────────────┘ │ ┌──────────────▼────────────────┐ │ getSkillToolCommands() │ │ 模型可见的最终列表 │ │ → prompt.ts budget 管理 │ │ → SkillTool system-reminder │ └───────────────────────────────┘

正文

清洗后的原始内容

Layer 5: Skills, Plugins & MCP Ecosystem

Claude Code 源码深度拆解 — 基于 @anthropic-ai/claude-code npm 包 sourcesContent 还原
分析范围: SkillTool (500行) + loadSkillsDir (750行) + pluginLoader (2700行) + MCP client (3000行) + 20 个卫星模块

生态系统架构概述

Claude Code 的可扩展能力由三个子系统协同构成:Skills(技能)、Plugins(插件)和 MCP(Model Context Protocol)。三者最终汇聚为统一的 Command 类型,由 commands.ts 中的 7 层优先级链按序加载。这一设计使得从用户自定义的 Markdown 文件到远程 MCP 服务器的工具,都以相同的接口呈现给模型。

三位一体架构

┌─────────────────────────────────────────────────────────────────┐
│                        SkillTool                                 │
│   模型调用入口 ── validateInput() → checkPermissions() → call()  │
└────────┬──────────────────────┬──────────────────────┬───────────┘
         │                      │                      │
    ┌────▼────┐            ┌────▼────┐            ┌────▼────┐
    │ Skills  │            │ Plugins │            │   MCP   │
    │         │            │         │            │         │
    │ .md文件 │            │plugin.json           │ client  │
    │ 前置声明│            │ 2700行  │            │ 3000行  │
    │ 目录格式│            │ 管理器  │            │ 4种传输 │
    └────┬────┘            └────┬────┘            └────┬────┘
         │                      │                      │
         ▼                      ▼                      ▼
    ┌─────────────────────────────────────────────────────────┐
    │              Command 统一类型 (types/command.ts)          │
    │  PromptCommand | LocalCommand | LocalJSXCommand          │
    └──────────────────────────┬──────────────────────────────┘
                               │
                    ┌──────────▼──────────┐
                    │   commands.ts       │
                    │   7 层优先级加载     │
                    │   去重 / 过滤 / 注入 │
                    └─────────────────────┘

三者的分工

维度 Skills Plugins MCP
定义格式 Markdown + YAML frontmatter plugin.json manifest + 目录结构 JSON-RPC 协议
来源 本地目录 / bundled / managed Marketplace / 本地 / SDK Stdio / SSE / WebSocket / HTTP
加载时机 启动时 + 动态发现 启动时(缓存优先) 连接时
安全边界 Shell 命令受 loadedFrom 控制 Manifest schema 验证 + 版本隔离 传输层认证 + OAuth
核心代码 loadSkillsDir.ts (750行) pluginLoader.ts (2700行) client.ts (3000行)

统一 Command 桥接

三个子系统产出的所有可调用能力,最终都被转化为 Command 联合类型。对模型而言,无论是用户在 ~/.claude/skills/ 下写的一段 Markdown,还是通过 Marketplace 安装的插件技能,还是远程 MCP 服务器暴露的 prompt,都以完全相同的 PromptCommand 形态出现在 SkillTool 的列表中。这种设计有一个深层后果:模型无法区分能力的来源,只能看到统一的 name + description + whenToUse。

命令加载优先级 7 层

loadAllCommands() 按以下顺序合并所有来源,先出现者在去重时优先保留:

  1. Bundled Skills -- 编译进二进制的内置技能(registerBundledSkill()
  2. Built-in Plugin Skills -- 内置插件提供的技能
  3. Skill Directory Commands -- .claude/skills/ 目录下的用户技能
  4. Workflow Commands -- 工作流脚本(feature gate: WORKFLOW_SCRIPTS
  5. Plugin Commands -- Marketplace 插件中 commands/ 目录的命令
  6. Plugin Skills -- Marketplace 插件中 skills/ 目录的技能
  7. Built-in Commands -- CLI 内置命令(/help, /clear, /compact 等)

在此基础上,getCommands() 还会将动态发现的技能(getDynamicSkills())插入到 Plugin Skills 之后、Built-in Commands 之前的位置。

命令类型系统 (types/command.ts)

200 行的 types/command.ts 定义了整个可扩展层的类型基石。理解 Command 类型就理解了 Skills、Plugins、MCP 三者如何统一。

PromptCommand -- 最核心的类型

PromptCommand 是所有 Skill / Plugin Skill / MCP Skill 的底层类型。以下逐字段说明:

字段类型说明
type'prompt'标记此命令为 prompt 类型,与 'local'(纯函数执行)和 'local-jsx'(React UI)区分
progressMessagestring执行时显示的进度文本,如 'running''loading'
contentLengthnumberMarkdown 内容长度(字符数),用于 token 估算
argNamesstring[]?声明式参数名列表,从 frontmatter arguments 字段解析
allowedToolsstring[]?技能执行时自动授权的工具列表
modelstring?模型覆盖,如 'haiku', 'sonnet', 'opus'
source枚举'userSettings' | 'projectSettings' | 'policySettings' | 'builtin' | 'mcp' | 'plugin' | 'bundled'
pluginInfoobject?插件元数据:{ pluginManifest, repository }
hooksHooksSettings?技能激活时注册的钩子
skillRootstring?技能资源的根目录路径
context'inline' | 'fork'?执行上下文:inline 展开到当前对话,fork 在子 Agent 中独立运行
agentstring?fork 模式下使用的 Agent 类型
effortEffortValue?推理努力级别,控制 thinking budget
pathsstring[]?条件激活的 glob 模式。设置后,技能仅在模型触及匹配文件时可见
getPromptForCommandasync function核心方法:接收 (args, context),返回 ContentBlockParam[]

CommandBase -- 共享基座

CommandBase 是所有三种命令类型共享的基础接口:

字段说明
name唯一标识符。Plugin 命令带命名空间前缀如 plugin_name:command
description描述文本,模型用它判断何时调用
hasUserSpecifiedDescription是否来自 frontmatter 的显式声明(区分自动提取)
availabilityCommandAvailability[]? -- 限制命令可见范围:'claude-ai''console'
isEnabled运行时判断函数(feature flag 等)
isHidden从 typeahead / help 中隐藏
aliases别名数组
whenToUse详细的使用场景描述,拼接到 description 后供模型参考
version技能版本号
disableModelInvocation禁止模型通过 SkillTool 主动调用
userInvocable用户是否可以通过 /skill-name 手动调用
loadedFrom加载来源:'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'
kind'workflow'? -- 标记工作流类型命令
immediate是否跳过队列立即执行
userFacingName显示名称函数,当 plugin 需要去掉前缀时使用

source vs loadedFrom 的区别

关键区分

source 表示配置来源的权限层级policySettings > userSettings > projectSettings),决定了权限覆盖优先级和 UI 中的来源标注。

loadedFrom 表示技能的物理来源(从哪种目录/机制加载),决定了安全策略(如 MCP 来源的技能禁止执行 shell 命令)和模型可见性过滤。

Command 联合类型

export type Command = CommandBase &
  (PromptCommand | LocalCommand | LocalJSXCommand)

三种子类型通过 type 字段区分:'prompt' 扩展为模型上下文,'local' 执行纯函数返回文本,'local-jsx' 渲染 React/Ink UI。

CommandAvailability 枚举

控制命令在不同认证/提供商环境中的可见性:

  • 'claude-ai' -- 仅 claude.ai OAuth 订阅用户(Pro/Max/Team/Enterprise)可见
  • 'console' -- 仅 Console API key 直连用户可见(排除 Bedrock/Vertex/Foundry 和自定义 base URL)

Frontmatter 解析 (frontmatterParser.ts)

371 行的 frontmatter 解析器是整个 Skill 系统的入口解码器。它将 Markdown 文件开头的 YAML 前置声明转化为类型安全的 FrontmatterData 对象,处理了 glob 模式中的花括号冲突、特殊字符引号逃逸、布尔值强制转换等诸多边缘情况。

parseFrontmatter() 完整流程

  1. 正则匹配 /^---\s*\n([\s\S]*?)---\s*\n?/ 提取 --- 之间的文本
  2. 首次 YAML 解析尝试(parseYaml()
  3. 若失败,调用 quoteProblematicValues() 对包含特殊字符的值自动加双引号,然后重试
  4. 若仍然失败,记录 debug 警告,返回空 frontmatter
  5. 返回 { frontmatter, content },content 是去掉 frontmatter 后的 Markdown 正文

特殊字符处理

YAML 特殊字符正则:/[{}[\]*&#!|>%@`]|: /

当检测到以下字符时自动添加双引号包裹:

  • { } -- flow mapping 指示符(与 glob 的 *.{ts,tsx} 冲突)
  • * & # ! -- YAML 锚点/注释/标签指示符
  • | > -- block scalar 指示符
  • : -- 键值分隔符(冒号后跟空格时才匹配,所以 12:34 和 URL 不受影响)

15+ Frontmatter 字段完整说明

字段类型作用
descriptionstring技能描述,是模型决定是否调用的主要信号
allowed-toolsstring | string[]技能执行期间自动授权的工具名称列表
when_to_usestring详细使用场景,拼接到 description 后
versionstring技能版本号(信息性)
user-invocableboolean用户是否能通过 /name 手动调用。默认 true
modelstring模型覆盖。'inherit' 表示使用父级模型
effortstring | int推理努力级别:low, medium, high, max 或整数
context'inline' | 'fork'执行上下文。fork 创建独立子 Agent
agentstringfork 时使用的 Agent 类型(如 'Bash'
argument-hintstring参数提示文本(typeahead 中灰色显示)
argumentsstring | string[]声明式参数名,支持 ${ARG_NAME} 替换
hide-from-slash-command-toolboolean-like从 SlashCommand 工具中隐藏(旧式兼容字段)
disable-model-invocationboolean禁止模型通过 SkillTool 主动调用
pathsstring | string[]条件激活的 glob 模式(gitignore 风格匹配)
shell'bash' | 'powershell'技能中 ! 块使用的 shell 类型。由作者决定,不读用户设置
hooksHooksSettings技能激活时注册的生命周期钩子(通过 HooksSchema 验证)

Brace Expansion

splitPathInFrontmatter() 处理逗号分隔的路径列表,同时支持花括号展开:

splitPathInFrontmatter("src/*.{ts,tsx}")
// → ["src/*.ts", "src/*.tsx"]

splitPathInFrontmatter("{a,b}/{c,d}")
// → ["a/c", "a/d", "b/c", "b/d"]

实现原理:先按不在花括号内的逗号拆分,再对每个部分递归调用 expandBraces()。花括号内的逗号不被视为分隔符(通过 braceDepth 计数器追踪嵌套深度)。

coerceDescriptionToString()

描述值的强制转换逻辑:

  • 字符串 → 原样返回(trim 后空字符串返回 null)
  • 数字/布尔值 → String() 转换
  • 数组/对象 → 无效,记录警告并返回 null
  • null/undefined → 返回 null(调用方回退到从 Markdown 正文提取)

Skill 加载 (loadSkillsDir.ts)

750 行的 loadSkillsDir.ts 是 Skill 子系统的主引擎。它从 5 种来源并行加载技能、去重、分离条件技能、支持动态发现,是理解 Claude Code 技能生命周期的关键文件。

loadSkillsFromSkillsDir() 完整流程

  1. 读取目录下的所有条目
  2. 过滤:只接受目录和符号链接(单 .md 文件在 /skills/ 目录下不被支持)
  3. 对每个目录,尝试读取 skill-name/SKILL.md
  4. 调用 parseFrontmatter() 解析 YAML 前置声明
  5. 调用 parseSkillFrontmatterFields() 提取所有字段
  6. 调用 parseSkillPaths() 处理条件路径模式
  7. 调用 createSkillCommand() 创建 Command 对象
  8. 返回 SkillWithPath[](包含文件路径,用于后续去重)

createSkillCommand() 工厂函数

这个工厂函数接受 20+ 个命名参数,返回一个满足 Command 类型的对象。其核心是 getPromptForCommand 闭包,执行以下管线:

  1. Base directory 前缀:如果有 baseDir,在内容前添加 "Base directory for this skill: {dir}"
  2. 参数替换substituteArguments(content, args, true, argumentNames) -- 将 ${ARG_NAME} 替换为实际参数值
  3. 变量替换${CLAUDE_SKILL_DIR} → 技能目录路径;${CLAUDE_SESSION_ID} → 当前会话 ID
  4. Shell 命令执行executeShellCommandsInPrompt() -- 执行 !`...` 内联 shell 块。MCP 来源的技能跳过此步骤(安全措施)
  5. 返回 [{ type: 'text', text: finalContent }]
安全设计

Shell 命令执行 (executeShellCommandsInPrompt) 通过 loadedFrom !== 'mcp' 进行门控。远程 MCP 技能的 Markdown 内容被视为不可信,其中的 ! 块永远不会被执行。这防止了通过 MCP prompt 注入恶意 shell 命令。

getSkillDirCommands() -- 主入口

这是一个 memoize 包装的异步函数,整合所有来源:

  1. 并行加载 5 种来源:
    • Managed Skills~/.claude-managed/.claude/skills/(组织管理策略推送)
    • User Skills~/.claude/skills/(用户全局技能)
    • Project Skills:从 cwd 向上遍历到 HOME 的所有 .claude/skills/
    • Additional Dir Skills--add-dir 参数指定的额外目录
    • Legacy Commands:旧式 /commands/ 目录(通过 loadSkillsFromCommandsDir
  2. 符号链接去重:对每个文件调用 realpath() 获取规范路径,相同物理文件只保留首次出现
  3. 条件技能分离:有 paths frontmatter 的技能被存入 conditionalSkills Map,不加入主列表
  4. 返回无条件技能列表

estimateSkillFrontmatterTokens()

仅基于 frontmatter 字段(name + description + whenToUse)估算 token 数,因为完整内容只在调用时加载。这使得技能列表的 token 预算可以在 prompt.ts 中精确控制。

动态技能发现

当模型执行文件操作时,discoverSkillDirsForPaths() 被触发:

  1. 从被操作文件的父目录开始,向上遍历到 cwd(不含 cwd 本身,因为 cwd 级别的技能在启动时已加载)
  2. 检查每个中间目录是否存在 .claude/skills/
  3. 跳过被 gitignore 的目录(防止 node_modules 中的恶意技能加载)
  4. 新发现的目录调用 addSkillDirectories() 加载并合并到 dynamicSkills Map
  5. 触发 skillsLoaded 信号,通知其他模块清除缓存

条件技能激活

activateConditionalSkillsForPaths() 使用 ignore 库(gitignore 风格匹配)检查文件路径是否匹配技能的 paths 模式:

// 技能 frontmatter: paths: "src/**/*.ts, tests/**"
// 当模型 Read/Write 匹配路径的文件时,技能被激活
if (skillIgnore.ignores(relativePath)) {
  dynamicSkills.set(name, skill)  // 移入可见列表
  conditionalSkills.delete(name)   // 从待激活列表移除
}

Bundled Skills (bundledSkills.ts)

221 行的 bundledSkills.ts 实现了编译进 CLI 二进制的内置技能注册机制。它的 files 字段延迟提取设计特别精巧——用 nonce 目录和 O_NOFOLLOW | O_EXCL 文件写入防御符号链接攻击。

BundledSkillDefinition 类型

字段类型说明
namestring技能名称
descriptionstring描述
aliasesstring[]?别名
whenToUsestring?使用场景描述
argumentHintstring?参数提示
allowedToolsstring[]?授权工具列表
modelstring?模型覆盖
disableModelInvocationboolean?禁止模型主动调用
userInvocableboolean?用户可调用(默认 true)
isEnabled() => boolean?Feature gate 函数
hooksHooksSettings?生命周期钩子
context'inline' | 'fork'?执行上下文
agentstring?Agent 类型
filesRecord<string, string>?需要提取到磁盘的参考文件(相对路径 → 内容)
getPromptForCommandasync function核心执行函数

files 延迟提取机制

当 bundled skill 定义了 files 字段时,这些文件并不在模块加载时写入磁盘,而是在技能首次被调用时才提取:

  1. getBundledSkillExtractDir(name) 返回确定性提取路径:{bundledSkillsRoot}/{skillName}/
  2. bundledSkillsRoot 包含 per-process nonce(进程级随机数),防止预创建的符号链接攻击
  3. 提取 Promise 被闭包内的 extractionPromise 变量缓存,并发调用者 await 同一个 Promise(不竞争写入)
  4. 文件写入使用 O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW 标志位:
    • O_EXCL:文件已存在则失败(不覆盖)
    • O_NOFOLLOW:最后一段是符号链接则失败
    • 权限 0o600(仅 owner 可读写),目录 0o700
  5. 提取成功后,在原始 prompt 内容前添加 "Base directory for this skill: {dir}"

内置技能列表

skills/bundled/ 目录可以看到以下 bundled skills(每个文件是一个独立的 registerBundledSkill() 调用):

文件技能
batch.ts批处理执行
claudeApi.ts + claudeApiContent.ts + claude-api/Claude API / Anthropic SDK 使用指南
claudeInChrome.tsChrome 浏览器集成技能
debug.ts调试辅助
dream.ts创意/规划
hunter.ts代码搜索猎手
keybindings.ts快捷键配置指南
loop.ts定时循环执行
remember.ts记忆管理
scheduleRemoteAgents.ts远程 Agent 调度
simplify.ts代码简化审查
skillify.ts技能创建器
stuck.ts卡住恢复
updateConfig.ts配置更新
verify.ts + verify/验证执行

registerBundledSkill() 实现

注册过程将 BundledSkillDefinition 转换为标准 Command 对象,固定 source: 'bundled'loadedFrom: 'bundled'contentLength: 0(bundled skills 不需要 token 估算)。注册发生在模块初始化阶段(import 时的副作用),在 commands.tsgetSkills() 中通过 getBundledSkills() 同步获取。

SkillTool 执行 (SkillTool.ts)

SkillTool 是模型调用技能的唯一入口。它的 500+ 行代码实现了从输入验证到权限检查到 inline/fork 双模执行的完整管线,并包含精细的遥测和安全控制。

getAllCommands()

SkillTool 需要同时看到本地技能和 MCP 技能。它从 context.getAppState().mcp.commands 中过滤出 loadedFrom === 'mcp' 的 prompt 命令,与 getCommands() 的本地结果合并,使用 uniqBy('name') 去重(本地优先)。

getSkillToolCommands() 过滤逻辑

模型可见的命令必须满足所有条件:

  • type === 'prompt'
  • !disableModelInvocation
  • source !== 'builtin'(内置命令通过其他方式提供给模型)
  • 以下至少满足一项:loadedFrom'bundled' | 'skills' | 'commands_DEPRECATED',或有 hasUserSpecifiedDescription,或有 whenToUse

validateInput() 验证链

  1. 去除前导 / 斜杠(兼容性处理)
  2. 远程 canonical skill 拦截(仅 ant 用户,EXPERIMENTAL_SKILL_SEARCH
  3. getAllCommands() 查找匹配命令(支持名称、别名匹配)
  4. 检查 disableModelInvocation 标志
  5. 检查 type === 'prompt'(非 prompt 命令不能通过 SkillTool 调用)

checkPermissions() 权限决策

权限决策遵循严格的优先级:

  1. Deny 规则:检查 getRuleByContentsForTool() 中的拒绝规则。支持精确匹配和前缀匹配(如 review:*
  2. Remote canonical skills:自动授权(ant-only,但在 deny 之后检查)
  3. Allow 规则:检查显式允许规则
  4. Safe properties 自动授权:如果技能只有 SAFE_SKILL_PROPERTIES 集合中的属性有值,自动允许(允许列表设计——新增属性默认需要权限)
  5. 兜底:返回 ask,提供精确匹配和前缀匹配的 allow rule 建议

call() -- 双模执行

Inline 执行(默认)

调用 processPromptSlashCommand(),将技能内容展开为 user message 注入当前对话。返回 newMessagescontextModifier(用于注入 allowedTools、model override、effort level)。

Fork 执行(context === 'fork')

executeForkedSkill() 创建独立子 Agent:

  1. 生成唯一 agentId
  2. 调用 prepareForkedCommandContext() 构建隔离上下文
  3. 通过 runAgent() 在独立 token 预算中执行
  4. 收集子 Agent 消息,汇报进度
  5. 提取结果文本,返回 { status: 'forked', result: ... }
  6. finally 块中调用 clearInvokedSkillsForAgent(agentId) 释放内存

Prompt Budget 管理 (prompt.ts)

技能列表占用上下文窗口的 1%(SKILL_BUDGET_CONTEXT_PERCENT = 0.01),默认 8000 字符。当技能过多时:

  • Bundled skills 的描述永远不被截断
  • 非 bundled skills 的描述按均分预算截断
  • 极端情况下,非 bundled skills 降级为仅显示名称(无描述)
  • 单条描述硬上限 250 字符(MAX_LISTING_DESC_CHARS

Plugin Manifest 与类型 (schemas.ts)

1000+ 行的 schemas.ts 使用 Zod v4 定义了插件生态系统的完整 schema 体系,从 marketplace 名称验证到 MCP 服务器配置到 LSP 集成,覆盖了 Anthropic 官方 marketplace 名称保护和 Unicode 同形攻击防御。

PluginManifest 完整结构

PluginManifestSchema 由多个 .partial() schema 交叉组合而成,每个子 schema 独立验证其关注的字段:

子 Schema字段说明
PluginManifestMetadataSchemaname, version, description, author, homepage, repository, license, keywords, dependencies插件元数据
PluginManifestCommandsSchemacommands额外命令路径(3 种格式)
PluginManifestSkillsSchemaskills额外技能目录路径
PluginManifestAgentsSchemaagents额外 Agent 文件路径
PluginManifestOutputStylesSchemaoutputStyles输出样式目录
PluginManifestHooksSchemahooks钩子配置(JSON 路径或内联)
PluginManifestMcpServerSchemamcpServersMCP 服务器配置(多格式)
PluginManifestLspServerSchemalspServersLSP 服务器配置
PluginManifestUserConfigSchemauserConfig用户可配置选项
PluginManifestChannelsSchemachannels消息通道声明(Telegram 等)

userConfig schema

每个选项字段支持以下属性:

  • type: 'string' | 'number' | 'boolean' | 'directory' | 'file'
  • title: 配置对话框中显示的标签(必填)
  • description: 字段下方的帮助文本(必填)
  • required: 验证空值是否失败
  • default: 用户不输入时的默认值
  • sensitive: 标记为敏感时,值存入 secure storage(macOS keychain),而非 settings.json
  • min / max: 数值类型的范围约束

Marketplace 名称安全

多层防御:

  1. 保留名称ALLOWED_OFFICIAL_MARKETPLACE_NAMES 定义了 8 个 Anthropic 官方名称
  2. 模式检测:正则 BLOCKED_OFFICIAL_NAME_PATTERN 拦截冒充变体
  3. Unicode 同形攻击NON_ASCII_PATTERN 检测非 ASCII 字符
  4. 来源验证validateOfficialNameSource() 强制保留名称只能从 anthropics GitHub org 安装

CommandMetadata (object-mapping 格式)

允许 marketplace 条目为命令提供富元数据:

{
  "commands": {
    "about": {
      "source": "./README.md",        // 或 "content": "inline markdown..."
      "description": "描述覆盖",
      "argumentHint": "[file]",
      "model": "haiku",
      "allowedTools": ["Read", "Grep"]
    }
  }
}

.refine() 验证 sourcecontent 互斥——必须有且只有一个。

Plugin 加载管理器 (pluginLoader.ts)

2700 行的 pluginLoader.ts 是整个插件子系统的心脏。它处理从 marketplace 发现、git 克隆、npm 安装、版本管理到插件激活的完整生命周期。

loadAllPlugins() 完整流程

  1. 调用 assemblePluginLoadResult() 并传入加载器函数
  2. 加载器内部调用 loadPluginsFromMarketplaces() 扫描所有配置的 marketplace
  3. 对每个 marketplace 条目,调用 loadPluginFromMarketplaceEntry() / loadPluginFromMarketplaceEntryCacheOnly()
  4. 并行加载 session 插件(--plugin-dir)和 built-in 插件
  5. 调用 mergeAndDeduplicatePlugins() 去重和冲突解决
  6. 应用 enable/disable 状态
  7. 返回 PluginLoadResult: { enabled: LoadedPlugin[], disabled: LoadedPlugin[], errors: PluginError[] }

3 种来源

来源格式加载方式
Marketplaceplugin_name@marketplace_name通过 marketplace.json 注册表发现,git clone + 版本缓存
Session-only--plugin-dir CLI 参数直接从本地目录加载,不缓存
Built-inBUILTIN_MARKETPLACE_NAME编译进二进制,从 plugins/bundled/ 注册

createPluginFromPath() -- 核心组装

从目录路径创建完整的 LoadedPlugin

  1. 加载 .claude-plugin/plugin.json(可选,缺失时创建默认 manifest)
  2. 并行检测 commands/agents/skills/output-styles/ 目录是否存在
  3. 处理 manifest 中声明的额外路径(3 种格式:单路径、数组、object-mapping)
  4. 加载 hooks/hooks.json 和 manifest 中的额外钩子
  5. 返回 { plugin: LoadedPlugin, errors: PluginError[] }

版本管理: 目录结构

~/.claude/plugins/
├── cache/
│   └── {marketplace}/
│       └── {plugin}/
│           └── {version}/       ← 版本隔离目录
│               ├── .claude-plugin/plugin.json
│               ├── commands/
│               ├── skills/
│               └── hooks/
├── npm-cache/                   ← npm 包全局缓存
└── data/
    └── {marketplace}/{plugin}/  ← 跨版本持久数据 (PLUGIN_DATA)

版本切换通过 symlink 实现:当前活动版本的目录通过 resolvePluginPath() 解析。旧版本保留但不被引用,最终由 orphan cleanup 清理。

Seed Cache

probeSeedCache() 在安装前先检查 seed 目录(预填充缓存),避免首次安装时的网络请求。这对企业环境中的离线部署尤其重要。

LoadedPlugin 类型核心字段

字段说明
name插件名称
manifest完整 PluginManifest
path插件目录绝对路径
source来源标识(name@marketplace
repository向后兼容别名,等于 source
enabled是否启用
commandsPath自动检测的 commands/ 目录路径
commandsPathsmanifest 声明的额外命令路径
commandsMetadataobject-mapping 格式的命令元数据
skillsPathskills/ 目录路径
skillsPathsmanifest 声明的额外技能路径
hooksConfig解析后的钩子配置
mcpServersMCP 服务器配置(缓存)

Plugin 命令与变量替换

loadPluginCommands.ts (510 行)

插件命令加载流程:

  1. getPluginCommands():遍历所有 enabled plugins,从 commandsPathcommandsPathscommandsMetadata 三个来源加载命令
  2. collectMarkdownFiles():递归收集目录中的 .md 文件
  3. transformPluginSkillFiles():处理目录中的 SKILL.md(目录模式优先于散文件)
  4. createPluginCommand():创建 Command 对象(与 createSkillCommand 功能对等,但增加了 plugin 变量替换)

命名空间规则

plugin_name:command_name           ← 直接子文件
plugin_name:subdir:command_name    ← 嵌套目录
plugin_name:skill_dir_name         ← SKILL.md 格式

getPluginSkills() 技能加载

并行处理每个插件的 skillsPathskillsPaths,调用 loadSkillsFromDirectory()。该函数首先检查根目录是否直接包含 SKILL.md(直接技能目录),否则扫描子目录。

pluginOptionsStorage.ts (400 行)

变量替换体系

变量替换为作用域
${CLAUDE_PLUGIN_ROOT}插件安装目录(版本隔离,更新时重建)MCP/LSP 配置、钩子命令、技能内容
${CLAUDE_PLUGIN_DATA}持久数据目录(跨版本保留)同上
${CLAUDE_SKILL_DIR}当前技能的目录(区别于插件根目录)技能内容
${CLAUDE_SESSION_ID}当前会话 ID技能内容
${user_config.KEY}用户配置值MCP/LSP 配置(直接替换)、技能内容(敏感值遮蔽)

敏感值处理

substituteUserConfigInContent() 用于技能/Agent 内容中的变量替换。与 MCP 配置中的 substituteUserConfigVariables() 不同,它对 sensitive: true 的字段返回占位符而非实际值:

// 技能内容中引用敏感字段
"${user_config.api_key}"
// → "[sensitive option 'api_key' not available in skill content]"

这是因为技能内容会被发送到模型上下文——不应该在 prompt 中暴露 API key 或密码。

存储分层

  • 非敏感值:settings.json → pluginConfigs[pluginId].options
  • 敏感值:secureStorage(macOS keychain 或 .credentials.json
  • 读取时合并,secureStorage 在冲突时胜出
  • 写入时先 secureStorage 再 settings.json(keychain 失败则 throw,不会写半个状态)

Plugin Hook 系统

200 行的 loadPluginHooks.ts 将插件的钩子配置转化为 Claude Code 原生的 hook matcher,支持 26 种生命周期事件。

完整事件类型列表

事件触发时机
PreToolUse工具调用前
PostToolUse工具调用后(成功)
PostToolUseFailure工具调用后(失败)
PermissionDenied权限被拒绝
PermissionRequest权限请求
Notification通知
UserPromptSubmit用户提交 prompt
SessionStart会话开始
SessionEnd会话结束
Stop模型停止
StopFailure停止失败
SubagentStart子 Agent 启动
SubagentStop子 Agent 停止
PreCompact上下文压缩前
PostCompact上下文压缩后
Setup初始设置
TeammateIdle队友空闲
TaskCreated任务创建
TaskCompleted任务完成
Elicitation引发式交互
ElicitationResult引发结果
ConfigChange配置变更
WorktreeCreate工作树创建
WorktreeRemove工作树移除
InstructionsLoaded指令加载完成
CwdChanged工作目录变更
FileChanged文件变更

convertPluginHooksToMatchers()

将插件的 hooksConfig 转换为注入了插件上下文的 PluginHookMatcher[]。每个 matcher 携带 pluginRootpluginNamepluginId 三个额外字段,使得 hook 执行器可以在正确的插件目录上下文中运行命令。

原子性 clear-then-register

Bug Fix 洞察 (gh-29767)

旧版本在 clearPluginHookCache() 中同时清除 memoize 缓存和 STATE.registeredHooks。这导致任何 clearAllCaches() 调用(插件管理、thinkback 等)都会清空所有插件钩子,直到下一次 loadPluginHooks() 才恢复。Stop 钩子没有保护机制——它们在清除后永远不会重新注册。修复后,清除和注册在 loadPluginHooks() 内部原子执行,旧钩子在新钩子就绪前保持有效。

Hot Reload

setupPluginHookHotReload() 监听 policySettings 变更(远程管理设置推送),通过 getPluginAffectingSettingsSnapshot() 比较 4 个字段的 JSON 快照:enabledPluginsextraKnownMarketplacesstrictKnownMarketplacesblockedMarketplaces。只有实际变化时才触发重加载。

MCP 客户端架构 (client.ts)

3000 行的 client.ts 实现了完整的 MCP 客户端栈:4 种传输层适配、OAuth 认证流、工具动态注册、prompt/skill 获取、资源管理和连接生命周期。

4 种传输层

类型使用场景
stdioStdioClientTransport本地进程(最常用):启动子进程,通过 stdin/stdout 通信
sseSSEClientTransport远程服务器(Server-Sent Events):单向推送 + HTTP POST 回传
httpStreamableHTTPClientTransport远程服务器(Streamable HTTP):双向 HTTP 流
wsWebSocketTransportWebSocket 全双工连接

此外还有特殊类型:sse-ide/ws-ide(IDE 代理)、sdk(SDK 控制)、claudeai-proxy(claude.ai 代理)。

MCPTool 包装器

MCPTool.ts 仅 78 行,定义了一个"壳"工具。所有实质性属性(namedescriptionpromptcallinputSchema)在 client.ts 中按 MCP 服务器的工具规格动态覆盖。关键标记 isMcp: true 使得权限系统和遥测可以区分 MCP 工具。

输入 schema 使用 z.object({}).passthrough()——接受任何对象,因为实际的 schema 由 MCP 服务器定义。

连接管理

ensureConnectedClient() 是连接的核心入口:

  • 检查连接缓存(per-server 的 MCPServerConnection 对象)
  • 处理 needs-auth 状态(OAuth 流程)
  • 处理 session expired 错误(McpSessionExpiredError:HTTP 404 + JSON-RPC -32001)
  • 超时管理:默认 ~27.8 小时(DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000

认证流

Claude.ai proxy 连接使用自定义 fetch wrapper(createClaudeAiProxyFetch):

  1. 在每个请求中注入 OAuth Bearer token
  2. 401 时调用 handleOAuth401Error() 尝试刷新 token
  3. 如果 token 确实更新了(非相同 token 重试),再次发送请求
  4. 15 分钟 needs-auth 缓存(MCP_AUTH_CACHE_TTL_MS)避免重复认证尝试

工具描述截断

OpenAPI 生成的 MCP 服务器常常产生 15-60KB 的工具描述。MAX_MCP_DESCRIPTION_LENGTH = 2048 字符硬上限防止模型上下文被单个工具描述淹没。

MCP Skill 融合

MCP prompts 可以作为 Skills 暴露给模型——这是一个通过 mcpSkillBuilders.ts 的注册模式实现的精妙设计,目的是打破 client.ts → loadSkillsDir.ts 的循环依赖。

mcpSkillBuilders.ts: 依赖解耦器

45 行的 mcpSkillBuilders.ts 是一个依赖图叶子模块。它只导入类型(不导入实现),提供一个 write-once 注册表:

// 类型声明
export type MCPSkillBuilders = {
  createSkillCommand: typeof createSkillCommand
  parseSkillFrontmatterFields: typeof parseSkillFrontmatterFields
}

// 注册表
let builders: MCPSkillBuilders | null = null

export function registerMCPSkillBuilders(b: MCPSkillBuilders): void {
  builders = b
}

export function getMCPSkillBuilders(): MCPSkillBuilders {
  if (!builders) throw new Error('Not registered yet')
  return builders
}

注册在 loadSkillsDir.ts 模块初始化时发生(文件底部的 registerMCPSkillBuilders({...}))。由于 loadSkillsDir.ts 通过 commands.ts 的静态导入在启动时被求值,注册一定在任何 MCP 服务器连接之前完成。

为什么不用 dynamic import?

Bun bundled binary 中的非字面量 dynamic import(await import(variable))会失败——specifier 被解析为 /$bunfs/root/... 路径而非源码树路径。字面量 dynamic import 可以工作,但 dependency-cruiser 会追踪它,而 loadSkillsDir 传递性地 reach 几乎所有模块,单条新边会扇出大量循环依赖违规。注册模式是唯一可行的解耦方案。

MCP_SKILLS Feature Gate

MCP prompt → Skill 的转换受 feature('MCP_SKILLS') 门控。开启后:

  • client.ts 中的 fetchMcpSkillsForClient 被条件 require 加载
  • MCP 服务器的 prompts 被转化为 loadedFrom: 'mcp' 的 Command 对象
  • 这些 Command 存储在 AppState.mcp.commands

SkillTool 中的 MCP Skills

getAllCommands() 从 AppState 中过滤 MCP skills(cmd.type === 'prompt' && cmd.loadedFrom === 'mcp'),与本地命令合并。getMcpSkillCommands() 进一步过滤掉 disableModelInvocation 的命令。

MCP skills 在 SkillTool 执行时有一个关键限制:executeShellCommandsInPrompt()loadedFrom !== 'mcp' 条件跳过,即远程 MCP skill 中的 shell 命令块永远不会被执行。

Plugin MCP 集成 (mcpPluginIntegration.ts)

180 行的集成层将插件的 MCP 服务器配置解析、变量替换、命名空间隔离和 MCPB (DXT) 文件处理统一在一起。

loadPluginMcpServers() 加载优先级

  1. 最低优先级.mcp.json 文件(插件目录下的标准 MCP 配置文件)
  2. 中等优先级manifest.mcpServers(plugin.json 中声明的服务器),支持多种格式:
    • 字符串:JSON 文件路径或 MCPB 文件路径
    • 数组:多个路径或内联配置的混合
    • 对象:直接的 MCP 服务器配置

MCPB (DXT) 文件处理

MCPB 是 MCP Bundle 格式(支持 .mcpb.dxt 扩展名),可以是本地路径或远程 URL。加载流程:

  1. 调用 loadMcpbFile() 下载/解压
  2. 检查是否需要用户配置(status: 'needs-config'
  3. 如果需要配置,静默跳过(不是错误),用户可通过 /plugin 菜单配置
  4. 使用 DXT manifest 中的 name 作为 server name

命名空间隔离

addPluginScopeToServers() 为每个插件的 MCP 服务器添加 plugin:{pluginName}:{serverName} 前缀,防止不同插件间的命名冲突。同时设置 scope: 'dynamic'pluginSource 用于追踪来源。

环境变量解析

resolvePluginMcpEnvironment() 按顺序解析:

  1. Plugin 专用变量(${CLAUDE_PLUGIN_ROOT} 等)
  2. 用户配置变量(${user_config.KEY}
  3. 通用环境变量(${HOME} 等,通过 expandEnvVarsInString()

对 stdio 类型的服务器,还自动注入 CLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATA 环境变量。

Channel 用户配置

Channels(Telegram、Discord 等消息通道)可以有独立于插件顶级 userConfig 的 per-server 配置。buildMcpUserConfig() 合并两层配置,channel-specific 在冲突时胜出。

命令加载与优先级 (commands.ts)

754 行的 commands.ts 是三个子系统的汇聚点。它定义了 80+ 个内置命令、加载优先级链、去重逻辑和模型可见性过滤。

loadAllCommands() 完整流程

const loadAllCommands = memoize(async (cwd) => {
  const [
    { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills },
    pluginCommands,
    workflowCommands,
  ] = await Promise.all([
    getSkills(cwd),         // 并行加载 4 类技能
    getPluginCommands(),    // 加载插件命令
    getWorkflowCommands?.(cwd),  // 加载工作流命令
  ])

  return [
    ...bundledSkills,       // 1. 编译内置(最高优先)
    ...builtinPluginSkills, // 2. 内置插件技能
    ...skillDirCommands,    // 3. 用户/项目技能目录
    ...workflowCommands,    // 4. 工作流脚本
    ...pluginCommands,      // 5. 插件命令
    ...pluginSkills,        // 6. 插件技能
    ...COMMANDS(),          // 7. 内置 CLI 命令
  ]
})

getCommands() -- 运行时过滤

虽然 loadAllCommands 被 memoize,getCommands() 每次调用都重新执行过滤(因为认证状态可能变化):

  1. meetsAvailabilityRequirement():检查 availability 枚举(claude-ai / console)
  2. isCommandEnabled():调用命令的 isEnabled() 函数(feature flag 等)
  3. 动态技能注入:getDynamicSkills() 的结果去重后插入到 Plugin Skills 之后、Built-in Commands 之前

getSkillToolCommands() vs getSlashCommandToolSkills()

函数用途过滤条件
getSkillToolCommands模型通过 SkillTool 可调用的所有命令type=prompt, !disableModelInvocation, source!='builtin', 且有描述或 whenToUse
getSlashCommandToolSkills技能统计/管理用同上但限定 loadedFrom 为 skills/plugin/bundled
getMcpSkillCommandsMCP 提供的技能loadedFrom='mcp', !disableModelInvocation(受 MCP_SKILLS gate 控制)

完整优先级图

    ┌───────────────────────────────┐
    │  loadAllCommands() 7层加载    │
    │                               │
    │  1. bundledSkills             │  registerBundledSkill()
    │  2. builtinPluginSkills       │  plugins/bundled/
    │  3. skillDirCommands          │  .claude/skills/ (5 sources)
    │  4. workflowCommands          │  WORKFLOW_SCRIPTS gate
    │  5. pluginCommands            │  plugin commands/ dir
    │  6. pluginSkills              │  plugin skills/ dir
    │  7. COMMANDS()                │  80+ built-in commands
    └──────────────┬────────────────┘
                   │
    ┌──────────────▼────────────────┐
    │  getCommands() 运行时过滤      │
    │                               │
    │  + meetsAvailabilityRequirement│
    │  + isCommandEnabled()         │
    │  + getDynamicSkills() inject  │
    │  + MCP skills (via AppState)  │
    └──────────────┬────────────────┘
                   │
    ┌──────────────▼────────────────┐
    │  getSkillToolCommands()       │
    │  模型可见的最终列表             │
    │  → prompt.ts budget 管理       │
    │  → SkillTool system-reminder   │
    └───────────────────────────────┘

缓存清理策略

函数清除范围
clearCommandMemoizationCaches()仅清除 loadAllCommands / getSkillToolCommands / getSlashCommandToolSkills / skillIndex 的 memoize 缓存。清除 skill 目录缓存(保留动态技能)
clearCommandsCache()上述全部 + clearPluginCommandCache + clearPluginSkillsCache + clearSkillCaches(完全重加载)

findCommand() 匹配逻辑

按以下优先级匹配:_.name === commandNamegetCommandName(_) === commandName(userFacingName)→ _.aliases?.includes(commandName)。第一个匹配项返回。

Skill 文件变更检测 (skillChangeDetector.ts)

312 行的变更检测器使用 chokidar 监视技能目录,处理了 Bun 的 FSWatcher 死锁 bug、debounce 防抖和 ConfigChange 钩子集成。

监视路径列表

  • ~/.claude/skills/ -- 用户全局技能
  • ~/.claude/commands/ -- 用户全局命令(旧式)
  • .claude/skills/ -- 项目技能
  • .claude/commands/ -- 项目命令(旧式)
  • --add-dir 目录下的 .claude/skills/

Bun deadlock workaround

Bun oven-sh/bun#27469

Bun 的原生 fs.watch()PathWatcherManager 存在死锁问题:在主线程关闭 watcher 时如果 File Watcher 线程正在投递事件,两者会在 __ulock_wait2 上永远阻塞。chokidar 在 depth: 2 模式下对大型技能树(数百个子目录)在 git 操作时可靠触发此 bug。

解决方案:在 Bun 环境下强制使用 usePolling: true(stat() 轮询),轮询间隔 2000ms。技能文件变更频率很低(手动编辑或 git 操作),2s 延迟换取零死锁风险。

Debounce 300ms

scheduleReload() 将快速连续的文件变更事件合并为单次重载:

function scheduleReload(changedPath: string): void {
  pendingChangedPaths.add(changedPath)
  if (reloadTimer) clearTimeout(reloadTimer)
  reloadTimer = setTimeout(async () => {
    // 1. 触发 ConfigChange 钩子(一次,不是每个文件一次)
    const results = await executeConfigChangeHooks('skills', paths[0]!)
    if (hasBlockingResult(results)) return  // 钩子可以阻止重载

    // 2. 清除所有缓存
    clearSkillCaches()
    clearCommandsCache()
    resetSentSkillNames()

    // 3. 通知监听者
    skillsChanged.emit()
  }, RELOAD_DEBOUNCE_MS)
}

缓存清理三件套

文件变更触发时,按序清理:

  1. clearSkillCaches() -- 清除 skill 目录加载的 memoize 缓存 + 条件技能 Map + 已激活技能名称 Set
  2. clearCommandsCache() -- 清除命令合并的 memoize 缓存
  3. resetSentSkillNames() -- 重置已发送给模型的技能名称集合(确保变更后的技能重新出现在 system-reminder 中)

Skill Search (实验性)

EXPERIMENTAL_SKILL_SEARCH feature gate 控制的实验性功能,允许模型从远程 canonical skills 仓库中发现和加载技能。

DiscoverSkillsTool

工具名称 discover_skills,允许模型主动搜索远程技能仓库。发现的技能以 _canonical_{slug} 前缀命名,与本地技能区分。

远程 Canonical Skills

SkillTool 中的远程技能处理流程:

  1. validateInput()stripCanonicalPrefix() 提取 slug
  2. 检查 getDiscoveredRemoteSkill(slug) 确认在本次会话中被发现过
  3. call() 中调用 loadRemoteSkill(slug, url) 从 AKI/GCS 加载(带本地缓存)
  4. 去掉 YAML frontmatter,注入 base directory header 和变量替换
  5. 通过 addInvokedSkill() 注册到 compaction-preservation 状态
  6. 直接包装为 user message 注入对话(不经过 processPromptSlashCommand

本地搜索索引

services/skillSearch/ 目录包含:

  • localSearch.ts -- 本地技能索引和搜索
  • remoteSkillLoader.ts -- 远程技能下载和缓存
  • remoteSkillState.ts -- 会话内的远程技能发现状态
  • telemetry.ts -- 搜索和加载遥测
  • featureCheck.ts -- Feature gate 检查
  • prefetch.ts -- 技能预加载

当前 mcpSkills.ts 是一个占位文件:export async function fetchMcpSkillsForClient() { return [] }。MCP Skills 的实际获取逻辑尚在开发中。

与 AI-Fleet 的对照

Claude Code 的 Layer 5 与 AI-Fleet 的技能系统在设计哲学上存在根本差异。Claude Code 追求工业级的插件生态和类型安全,AI-Fleet 追求极简的个人工具链。以下逐维度对比。

Frontmatter 字段对比

维度Claude CodeAI-Fleet
字段数量15+ 个 frontmatter 字段,每个有完整的解析/验证/回退逻辑少量非标准字段,无 schema 验证
执行上下文context: 'inline' | 'fork',fork 创建独立子 Agent无此概念
条件激活paths 字段 + gitignore 风格匹配无条件激活机制
Shell 控制shell: 'bash' | 'powershell',技能作者决定无此字段
模型覆盖model + effort 精确控制无模型覆盖
钩子支持Frontmatter 内可声明 26 种生命周期钩子无钩子系统

Plugin 系统对比

维度Claude CodeAI-Fleet
代码量pluginLoader.ts 2700 行 + 配套模块数千行无 plugin 系统
来源管理Marketplace 注册表、git clone、npm install、版本隔离手动目录管理
安全模型Manifest schema 验证、marketplace 名称保护、Unicode 攻击防御、路径遍历检查手动审查(skill-vetter 技能)
变量替换5 种变量 + 敏感值分层存储无变量替换机制
生命周期钩子26 种事件、原子注册、hot reload通过 settings.json hooks 手动配置
用户配置Schema-driven 配置对话框、secure storage 分层无此机制

MCP 集成对比

维度Claude CodeAI-Fleet
MCP Skill 融合MCP prompts → PromptCommand,受 MCP_SKILLS gate 控制,安全限制 shell 执行MCP 工具直接调用,无 Skill 层中介
认证OAuth、Bearer token、session 管理、15min auth cache依赖 .mcp.json 静态配置
Plugin-MCP 桥接插件可声明 MCP 服务器,自动命名空间隔离和变量替换无此桥接
传输层4 种(Stdio/SSE/HTTP/WebSocket)+ 特殊类型Stdio 为主

文件监视对比

维度Claude CodeAI-Fleet
实现chokidar + Bun 死锁 workaround + debounce 300ms + ConfigChange 钩子无自动监视,手动重载
缓存策略多层 memoize + 选择性清除(clearCommandMemoizationCaches vs clearCommandsCache)无缓存体系
动态发现文件操作时自动发现子目录中的技能 + 条件技能激活无动态发现
核心差异总结

Claude Code 的 Layer 5 是为多租户平台级生态设计的——它需要处理不可信的第三方插件、跨组织的策略管控、marketplace 供应链安全。AI-Fleet 是为单用户私有工具链设计的——它追求最小配置、最大灵活性、零 overhead。两者解决的是不同层次的问题,但 Claude Code 的很多设计模式(注册模式解耦循环依赖、frontmatter 渐进式字段解析、条件技能激活)值得 AI-Fleet 在需要时借鉴。

* * *

Layer 5: Skills, Plugins & MCP Ecosystem -- Claude Code 源码深度拆解

Maurice | maurice_wen@proton.me

2026-03-31 | 基于 Claude Code 源码逆向分析