diff --git a/claude-code-best/docs/agent/coordinator-and-swarm.mdx b/claude-code-best/docs/agent/coordinator-and-swarm.md similarity index 100% rename from claude-code-best/docs/agent/coordinator-and-swarm.mdx rename to claude-code-best/docs/agent/coordinator-and-swarm.md diff --git a/claude-code-best/docs/agent/sub-agents.mdx b/claude-code-best/docs/agent/sub-agents.md similarity index 100% rename from claude-code-best/docs/agent/sub-agents.mdx rename to claude-code-best/docs/agent/sub-agents.md diff --git a/claude-code-best/docs/agent/worktree-isolation.mdx b/claude-code-best/docs/agent/worktree-isolation.md similarity index 100% rename from claude-code-best/docs/agent/worktree-isolation.mdx rename to claude-code-best/docs/agent/worktree-isolation.md diff --git a/claude-code-best/docs/context/compaction.mdx b/claude-code-best/docs/context/compaction.md similarity index 100% rename from claude-code-best/docs/context/compaction.mdx rename to claude-code-best/docs/context/compaction.md diff --git a/claude-code-best/docs/context/project-memory.mdx b/claude-code-best/docs/context/project-memory.md similarity index 100% rename from claude-code-best/docs/context/project-memory.mdx rename to claude-code-best/docs/context/project-memory.md diff --git a/claude-code-best/docs/context/system-prompt.mdx b/claude-code-best/docs/context/system-prompt.md similarity index 100% rename from claude-code-best/docs/context/system-prompt.mdx rename to claude-code-best/docs/context/system-prompt.md diff --git a/claude-code-best/docs/context/token-budget.mdx b/claude-code-best/docs/context/token-budget.md similarity index 100% rename from claude-code-best/docs/context/token-budget.mdx rename to claude-code-best/docs/context/token-budget.md diff --git a/claude-code-best/docs/conversation/multi-turn.mdx b/claude-code-best/docs/conversation/multi-turn.md similarity index 100% rename from claude-code-best/docs/conversation/multi-turn.mdx rename to claude-code-best/docs/conversation/multi-turn.md diff --git a/claude-code-best/docs/conversation/streaming.mdx b/claude-code-best/docs/conversation/streaming.md similarity index 100% rename from claude-code-best/docs/conversation/streaming.mdx rename to claude-code-best/docs/conversation/streaming.md diff --git a/claude-code-best/docs/conversation/the-loop.mdx b/claude-code-best/docs/conversation/the-loop.md similarity index 100% rename from claude-code-best/docs/conversation/the-loop.mdx rename to claude-code-best/docs/conversation/the-loop.md diff --git a/claude-code-best/docs/diagrams/agent-loop-simple.md b/claude-code-best/docs/diagrams/agent-loop-simple.md new file mode 100644 index 0000000..0a67fea --- /dev/null +++ b/claude-code-best/docs/diagrams/agent-loop-simple.md @@ -0,0 +1,34 @@ +--- +tags: + - claude-code + - mermaid +create time: 2026-06-08 00:00 +--- + +# Agent Loop 简易流程 + +## 概述 + +Claude Code Agent 的核心执行循环:输入经过 Context 管理后交给 LLM 流式输出,若返回 `tool_use` 则执行工具并将结果回注 Context,循环往复;否则结束。 + +## 正文 + +```mermaid +flowchart TB + START((输入)) --> CTX["Context 管理"] + CTX --> LLM["LLM 流式输出"] + LLM --> TC{"tool_use?"} + + TC --> |是| EXEC["执行工具"] + EXEC --> CTX + + TC --> |否| DONE((完成)) + + classDef proc fill:#eef,stroke:#66c,color:#224 + classDef decision fill:#fee,stroke:#c66,color:#422 + classDef io fill:#eff,stroke:#6cc,color:#244 + + class CTX,LLM,EXEC proc + class TC decision + class START,DONE io +``` \ No newline at end of file diff --git a/claude-code-best/docs/diagrams/agent-loop.md b/claude-code-best/docs/diagrams/agent-loop.md new file mode 100644 index 0000000..47ea79d --- /dev/null +++ b/claude-code-best/docs/diagrams/agent-loop.md @@ -0,0 +1,42 @@ +```mermaid +flowchart TB + START((输入)) --> CTX["Context 管理"] + CTX --> PRE["Pre-sampling Hook"] + PRE --> LLM["LLM 流式输出"] + LLM --> TC{tool_use?} + + TC --> |是| PERM{需权限?} + PERM --> |是| USER["👤 用户审批"] + USER --> |allow| TOOL_PRE + USER --> |deny| DENIED["拒绝"] + PERM --> |否| TOOL_PRE["Pre-tool Hook"] + TOOL_PRE --> EXEC["并发执行工具"] + EXEC --> TOOL_POST["Post-tool Hook"] + TOOL_POST --> CTX + DENIED --> CTX + + TC --> |否| POST["Post-sampling Hook"] + POST --> STOP{"Stop Hook"} + STOP --> |不通过| CTX + STOP --> |通过| BUDGET{"Token Budget"} + BUDGET --> |继续| CTX + BUDGET --> |完成| DONE((完成)) + + subgraph SUB["子 Agent"] + FORK["AgentTool"] --> RECURSE["递归调用"] + end + + EXEC -.-> FORK + + classDef proc fill:#eef,stroke:#66c,color:#224 + classDef decision fill:#fee,stroke:#c66,color:#422 + classDef hook fill:#ffe,stroke:#cc6,color:#442 + classDef io fill:#eff,stroke:#6cc,color:#244 + classDef sub fill:#efe,stroke:#6a6,color:#242 + + class CTX,LLM,EXEC proc + class TC,PERM,STOP,BUDGET decision + class PRE,TOOL_PRE,TOOL_POST,POST hook + class START,DONE,USER,DENIED io + class FORK,RECURSE sub +``` \ No newline at end of file diff --git a/claude-code-best/docs/extensibility/custom-agents.mdx b/claude-code-best/docs/extensibility/custom-agents.md similarity index 100% rename from claude-code-best/docs/extensibility/custom-agents.mdx rename to claude-code-best/docs/extensibility/custom-agents.md diff --git a/claude-code-best/docs/extensibility/hooks.mdx b/claude-code-best/docs/extensibility/hooks.md similarity index 100% rename from claude-code-best/docs/extensibility/hooks.mdx rename to claude-code-best/docs/extensibility/hooks.md diff --git a/claude-code-best/docs/extensibility/mcp-configuration.mdx b/claude-code-best/docs/extensibility/mcp-configuration.md similarity index 100% rename from claude-code-best/docs/extensibility/mcp-configuration.mdx rename to claude-code-best/docs/extensibility/mcp-configuration.md diff --git a/claude-code-best/docs/extensibility/mcp-protocol.mdx b/claude-code-best/docs/extensibility/mcp-protocol.md similarity index 100% rename from claude-code-best/docs/extensibility/mcp-protocol.mdx rename to claude-code-best/docs/extensibility/mcp-protocol.md diff --git a/claude-code-best/docs/extensibility/skills.mdx b/claude-code-best/docs/extensibility/skills.md similarity index 100% rename from claude-code-best/docs/extensibility/skills.mdx rename to claude-code-best/docs/extensibility/skills.md diff --git a/claude-code-best/docs/features/buddy.mdx b/claude-code-best/docs/features/buddy.md similarity index 100% rename from claude-code-best/docs/features/buddy.mdx rename to claude-code-best/docs/features/buddy.md diff --git a/claude-code-best/docs/features/debug-mode.mdx b/claude-code-best/docs/features/debug-mode.md similarity index 100% rename from claude-code-best/docs/features/debug-mode.mdx rename to claude-code-best/docs/features/debug-mode.md diff --git a/claude-code-best/docs/features/extensibility/custom-agents.md b/claude-code-best/docs/features/extensibility/custom-agents.md new file mode 100644 index 0000000..2846f5d --- /dev/null +++ b/claude-code-best/docs/features/extensibility/custom-agents.md @@ -0,0 +1,211 @@ +--- +title: "自定义 Agent - 从 Markdown 到运行时的完整链路" +description: "揭秘 Claude Code 自定义 Agent 完整链路:Agent 定义的 Markdown 数据模型、三种加载来源、工具过滤策略和与 AgentTool 的联动机制。" +keywords: ["自定义 Agent", "Agent 定义", "Markdown Agent", "Agent 配置", "角色定制"] +--- + +{/* 本章目标:揭示 Agent 定义的完整数据模型、加载发现机制、工具过滤和与 AgentTool 的联动 */} + +## Agent 定义的三种来源 + +Claude Code 的 Agent 不仅仅来自用户自定义——系统有三类来源,按优先级合并: + +| 来源 | 位置 | 优先级 | +|------|------|--------| +| **Built-in** | `packages/builtin-tools/src/tools/AgentTool/built-in/` 硬编码 | 最低(可被覆盖) | +| **Plugin** | 通过插件系统注册 | 中 | +| **User/Project/Policy** | `.claude/agents/*.md` 或 settings.json | 最高 | + +合并逻辑在 `getActiveAgentsFromList()` 中:按 `agentType` 去重,后者覆盖前者。这意味着你可以在 `.claude/agents/` 中放一个 `Explore.md` 来完全替换内置的 Explore Agent。 + +## Markdown Agent 文件的完整格式 + +```markdown +--- +# === 必需字段 === +name: "reviewer" # Agent 标识(agentType) +description: "Code review specialist, read-only analysis" + +# === 工具控制 === +tools: "Read,Glob,Grep,Bash" # 允许的工具列表(逗号分隔) +disallowedTools: "Write,Edit" # 显式禁止的工具 + +# === 模型配置 === +model: "haiku" # 指定模型(或 "inherit" 继承主线程) +effort: "high" # 推理努力程度:low/medium/high 或整数 + +# === 行为控制 === +maxTurns: 10 # 最大 agentic 轮次 +permissionMode: "plan" # 权限模式:plan/bypassPermissions 等 +background: true # 始终作为后台任务运行 +initialPrompt: "/search TODO" # 首轮用户消息前缀(支持斜杠命令) + +# === 隔离与持久化 === +isolation: "worktree" # 在独立 git worktree 中运行 +memory: "project" # 持久记忆范围:user/project/local + +# === MCP 服务器 === +mcpServers: + - "slack" # 引用已配置的 MCP 服务器 + - database: # 内联定义 + command: "npx" + args: ["mcp-db"] + +# === Hooks === +hooks: + PreToolUse: + - command: "audit-log.sh" + timeout: 5000 + +# === Skills === +skills: "code-review,security-review" # 预加载的 skills(逗号分隔) + +# === 显示 === +color: "blue" # 终端中的 Agent 颜色标识 +--- + +你是代码审查专家。你的职责是... + +(正文内容 = system prompt) +``` + +### 字段解析细节 + +- **`tools`**:通过 `parseAgentToolsFromFrontmatter()` 解析,支持逗号分隔字符串或数组 +- **`model: "inherit"`**:使用主线程的模型(区分大小写,只有小写 "inherit" 有效) +- **`memory`**:启用后自动注入 `Write`/`Edit`/`Read` 工具(即使 `tools` 未包含),并在 system prompt 末尾追加 memory 指令 +- **`isolation: "remote"`**:仅在 Anthropic 内部可用(`USER_TYPE === 'ant'`),外部构建只支持 `worktree` +- **`background`**:`true` 使 Agent 始终在后台运行,主线程不等待结果 + +## 加载与发现机制 + +`getAgentDefinitionsWithOverrides()`(被 `memoize` 缓存)执行完整的发现流程: + +``` +1. 加载 Markdown 文件 + ├── loadMarkdownFilesForSubdir('agents', cwd) + │ ├── ~/.claude/agents/*.md (用户级,source = 'userSettings') + │ ├── .claude/agents/*.md (项目级,source = 'projectSettings') + │ └── managed/policy sources (策略级,source = 'policySettings') + │ + └── 每个 .md 文件: + ├── 解析 YAML frontmatter + ├── 正文作为 system prompt + ├── 校验必需字段(name, description) + ├── 静默跳过无 frontmatter 的 .md 文件(可能是参考文档) + └── 解析失败 → 记录到 failedFiles,不阻塞其他 Agent + +2. 并行加载 Plugin Agents + └── loadPluginAgents() → memoized + +3. 初始化 Memory Snapshots(如果 AGENT_MEMORY_SNAPSHOT 启用) + └── initializeAgentMemorySnapshots() + +4. 合并 Built-in + Plugin + Custom + └── getActiveAgentsFromList() → 按 agentType 去重,后者覆盖前者 + +5. 分配颜色 + └── setAgentColor(agentType, color) → 终端 UI 中区分不同 Agent +``` + +## 工具过滤的实现 + +当 Agent 被派生时,`AgentTool` 根据定义中的 `tools` / `disallowedTools` 过滤可用工具列表: + +``` +全部工具 + ↓ disallowedTools 移除 + ↓ tools 白名单过滤(如果指定) +可用工具 +``` + +- **`tools` 未指定**:Agent 可以使用所有工具(默认全能) +- **`tools` 指定**:只能使用列出的工具 +- **`disallowedTools`**:即使 `tools` 未指定,这些工具也被禁止 +- **自动注入**:`memory` 启用时自动添加 `Write`/`Edit`/`Read` + +以内置 Explore Agent 为例: + +```typescript +// packages/builtin-tools/src/tools/AgentTool/built-in/exploreAgent.ts +disallowedTools: [ + 'Agent', // 不能嵌套调用 Agent + 'ExitPlanMode', // 不需要 plan mode + 'FileEdit', // 只读 + 'FileWrite', // 只读 + 'NotebookEdit', // 只读 +] +``` + +## System Prompt 的注入方式 + +Agent 的 system prompt 通过 `getSystemPrompt()` 闭包延迟生成: + +```typescript +// Markdown Agent +getSystemPrompt: () => { + if (isAutoMemoryEnabled() && memory) { + return systemPrompt + '\n\n' + loadAgentMemoryPrompt(agentType, memory) + } + return systemPrompt +} +``` + +这意味着: +1. **Markdown 正文 = 完整的 system prompt**——不是追加,而是替换默认 prompt +2. **Memory 指令**在 memory 启用时自动追加到末尾 +3. **闭包延迟计算**——memory 状态可能在文件加载后才变化 + +对于 Built-in Agent,`getSystemPrompt` 接受 `toolUseContext` 参数,可以根据运行时状态(如是否使用嵌入式搜索工具)动态调整 prompt 内容。 + +## 与 AgentTool 的联动 + +当主 Agent 需要派生子 Agent 时: + +``` +AgentTool.call({ subagent_type: "reviewer", ... }) + ↓ +1. 从 agentDefinitions.activeAgents 查找 agentType === "reviewer" + ↓ +2. 检查 requiredMcpServers(如果 Agent 要求特定 MCP 服务器) + ↓ +3. 过滤工具列表(tools / disallowedTools) + ↓ +4. 解析模型: + - "inherit" → 使用主线程模型 + - 具体模型名 → 直接使用 + - 未指定 → 主线程模型 + ↓ +5. 解析权限模式(permissionMode) + ↓ +6. 构建隔离环境(如果 isolation === "worktree") + ↓ +7. 注入 system prompt(getSystemPrompt()) + ↓ +8. 注入 initialPrompt(如果定义了) + ↓ +9. 启动子 Agent 循环(forkSubagent / runAgent) +``` + +## 内置 Agent 参考 + +| Agent | agentType | 角色 | 工具限制 | 模型 | +|-------|-----------|------|---------|------| +| **General Purpose** | `general-purpose` | 默认子 Agent | 全部工具 | 主线程模型 | +| **Explore** | `Explore` | 代码搜索专家 | 只读(无 Write/Edit) | haiku(外部) | +| **Plan** | `Plan` | 规划专家 | 只读 + ExitPlanMode | inherit | +| **Verification** | `verification` | 结果验证 | 由 feature flag 控制 | — | +| **Code Guide** | `claude-code-guide` | Claude Code 使用指南 | 只读 | — | +| **Statusline Setup** | `statusline-setup` | 终端状态栏配置 | 有限 | — | + +SDK 入口(`sdk-ts`/`sdk-py`/`sdk-cli`)不加载 Code Guide Agent。环境变量 `CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS` 可以完全禁用内置 Agent,给 SDK 用户提供空白画布。 + +## Agent Memory:持久化的 Agent 状态 + +当 `memory` 字段启用时,Agent 获得跨会话的持久记忆: + +- **`local`**:当前项目、当前用户有效 +- **`project`**:当前项目所有用户共享 +- **`user`**:所有项目共享 + +Memory 通过 `loadAgentMemoryPrompt()` 注入到 system prompt 末尾,包含读写记忆的指令。Agent Memory Snapshot 机制在项目间同步 `user` 级记忆。 diff --git a/claude-code-best/docs/features/extensibility/hooks.md b/claude-code-best/docs/features/extensibility/hooks.md new file mode 100644 index 0000000..438f546 --- /dev/null +++ b/claude-code-best/docs/features/extensibility/hooks.md @@ -0,0 +1,253 @@ +--- +title: "Hooks 生命周期钩子 - 执行引擎与拦截协议" +description: "从源码角度解析 Claude Code Hooks 系统:27 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。" +keywords: ["Hooks", "生命周期钩子", "拦截器", "PreToolUse", "Hook 协议"] +--- + +{/* 本章目标:从源码角度揭示 Hook 的执行引擎、匹配机制、返回值协议和生命周期管理 */} + +## 27 种 Hook 事件 + +Claude Code 定义了 27 种 Hook 事件(`HOOK_EVENTS` 数组,`src/entrypoints/sdk/coreTypes.ts`),覆盖完整的 Agent 生命周期: + +| 阶段 | 事件 | 触发时机 | 匹配字段 | +|------|------|---------|---------| +| **会话** | `SessionStart` | 会话启动 | `source` | +| | `SessionEnd` | 会话结束 | `reason` | +| | `Setup` | 初始化完成 | `trigger` | +| **用户交互** | `UserPromptSubmit` | 用户提交消息 | — | +| | `Stop` | Agent 停止响应 | — | +| | `StopFailure` | Agent 停止失败 | `error` | +| **工具执行** | `PreToolUse` | 工具调用前 | `tool_name` | +| | `PostToolUse` | 工具调用后(成功) | `tool_name` | +| | `PostToolUseFailure` | 工具调用后(失败) | `tool_name` | +| **权限** | `PermissionRequest` | 权限请求 | `tool_name` | +| | `PermissionDenied` | 权限被拒 | `tool_name` | +| **子 Agent** | `SubagentStart` | 子 Agent 启动 | `agent_type` | +| | `SubagentStop` | 子 Agent 停止 | `agent_type` | +| **压缩** | `PreCompact` | 上下文压缩前 | `trigger` | +| | `PostCompact` | 上下文压缩后 | `trigger` | +| **协作** | `TeammateIdle` | Teammate 空闲 | — | +| | `TaskCreated` | 任务创建 | — | +| | `TaskCompleted` | 任务完成 | — | +| **MCP** | `Elicitation` | MCP 服务器请求用户输入 | `mcp_server_name` | +| | `ElicitationResult` | Elicitation 结果返回 | `mcp_server_name` | +| **通知** | `Notification` | 系统通知事件 | `notification_type` | +| **环境** | `ConfigChange` | 配置变更 | `source` | +| | `CwdChanged` | 工作目录变更 | — | +| | `FileChanged` | 文件变更 | `file_path` | +| | `InstructionsLoaded` | 指令加载 | `load_reason` | +| | `WorktreeCreate` / `WorktreeRemove` | Worktree 操作 | — | + +## 6 种 Hook 类型 + +Hooks 配置支持 6 种执行方式,类型定义分布在 3 个文件中: + +- **可持久化类型**(`command`、`prompt`、`agent`、`http`)— Zod schema 定义在 `src/schemas/hooks.ts`,通过 `z.discriminatedUnion('type', [...])` 声明 +- **callback 类型** — TypeScript 接口定义在 `src/types/hooks.ts`,用于 SDK 注册的内部 JS 函数 +- **function 类型** — 定义在 `src/utils/hooks/sessionHooks.ts`,用于运行时动态注册的函数 Hook + +| 类型 | 执行方式 | 适用场景 | +|------|---------|---------| +| `command` | Shell 命令(bash/PowerShell) | 通用脚本、CI 检查 | +| `prompt` | 注入到 AI 上下文 | 代码规范提醒 | +| `agent` | 启动子 Agent 执行 | 复杂分析任务 | +| `http` | HTTP 请求 | 远程服务、Webhook | +| `callback` | 内部 JS 函数 | 系统内置 Hook | +| `function` | 运行时注册的函数 Hook | Agent/Skill 内部使用 | + +## 执行引擎:execCommandHook + +`execCommandHook()`(`src/utils/hooks.ts`,`execCommandHook` 函数)是命令型 Hook 的执行核心: + +``` +execCommandHook(hook, hookEvent, hookName, jsonInput, signal) + ├── Shell 选择: hook.shell ?? DEFAULT_HOOK_SHELL + │ ├── bash: spawn(cmd, [], { shell: gitBashPath | true }) + │ └── powershell: spawn(pwsh, ['-NoProfile', '-NonInteractive', '-Command', cmd]) + ├── 变量替换 + │ ├── ${CLAUDE_PLUGIN_ROOT} → pluginRoot 路径 + │ ├── ${CLAUDE_PLUGIN_DATA} → plugin 数据目录 + │ └── ${user_config.X} → 用户配置值 + ├── 环境变量注入 + │ ├── CLAUDE_PROJECT_DIR + │ ├── CLAUDE_ENV_FILE(SessionStart/Setup/CwdChanged/FileChanged) + │ └── CLAUDE_PLUGIN_OPTION_*(plugin options) + ├── stdin 写入: jsonInput + '\n' + ├── 超时: hook.timeout * 1000 ?? 600000ms(10分钟) + └── 异步检测: 检查 stdout 首行是否为 {"async":true} +``` + +### 异步 Hook 的检测协议 + +Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`isAsyncHookJSONOutput` 检测 + `executeInBackground` 调用): + +```typescript +const firstLine = firstLineOf(stdout).trim() +if (isAsyncHookJSONOutput(parsed)) { + executeInBackground({ + processId: `async_hook_${child.pid}`, + asyncResponse: parsed, + ... + }) +} +``` + +后台 Hook 通过 `registerPendingAsyncHook()` 注册到 `AsyncHookRegistry`,完成后通过 `enqueuePendingNotification()` 通知主线程。 + +### asyncRewake:Hook 唤醒模型 + +`asyncRewake` 模式的 Hook 绕过 `AsyncHookRegistry`。当 Hook 退出码为 2 时,通过 `enqueuePendingNotification()` 以 `task-notification` 模式注入消息,唤醒空闲的模型(通过 `useQueueProcessor`)或在忙碌时注入 `queued_command` 附件。 + +## Hook 输出的 JSON Schema + +同步 Hook 的输出遵循严格的 Zod schema(`syncHookResponseSchema`,定义在 `src/types/hooks.ts`,`hookJSONOutputSchema` 定义在 `src/schemas/hooks.ts`): + +```json +{ + "continue": false, // 是否继续执行 + "suppressOutput": true, // 隐藏 stdout + "stopReason": "安全检查失败", // continue=false 时的原因 + "decision": "approve" | "block", // 全局决策 + "reason": "原因说明", // 决策原因 + "systemMessage": "警告内容", // 注入到上下文的系统消息 + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow" | "deny" | "ask", + "permissionDecisionReason": "匹配了安全规则", + "updatedInput": { ... }, // 修改后的工具输入 + "additionalContext": "额外上下文" // 注入到对话 + } +} +``` + +### 各事件的 hookSpecificOutput + +| 事件 | 专有字段 | 作用 | +|------|---------|------| +| `PreToolUse` | `permissionDecision`, `permissionDecisionReason`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 | +| `PostToolUse` | `additionalContext`, `updatedMCPToolOutput` | 修改 MCP 工具输出 | +| `PostToolUseFailure` | `additionalContext` | 失败后注入上下文 | +| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 | +| `SessionStart` | `additionalContext`, `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 | +| `PermissionRequest` | `decision`(含 `allow`/`deny` 子字段) | 权限请求的 Hook 决策 | +| `PermissionDenied` | `retry` | 指示是否重试 | +| `SubagentStart` | `additionalContext` | 子 Agent 启动时注入上下文 | +| `Elicitation` | `action`, `content` | 控制用户输入对话框 | +| `ElicitationResult` | `action`, `content` | Elicitation 结果处理 | +| `Notification` | `additionalContext` | 通知事件注入上下文 | +| `Setup` | `additionalContext` | 初始化时注入上下文 | +| `CwdChanged` | `watchPaths` | 目录变更后更新监控路径 | +| `FileChanged` | `watchPaths` | 文件变更后更新监控路径 | +| `WorktreeCreate` | `worktreePath` | Worktree 创建通知 | + +## Hook 匹配机制:getMatchingHooks + +`getMatchingHooks()`(`src/utils/hooks.ts`,`getMatchingHooks` 函数)负责从所有来源中查找匹配的 Hook: + +### 多来源合并 + +``` +getHooksConfig() + ├── getHooksConfigFromSnapshot() ← settings.json 中的 Hook(user/project/local) + ├── getRegisteredHooks() ← SDK 注册的 callback Hook + ├── getSessionHooks() ← Agent/Skill 前置注册的 session Hook + └── getSessionFunctionHooks() ← 运行时 function Hook +``` + +### 匹配规则 + +`matcher` 字段支持三种模式(`matchesPattern()` 函数,`src/utils/hooks.ts`): + +``` +"Write" → 精确匹配 +"Write|Edit" → 管道分隔的多值匹配 +"^Bash(git.*)" → 正则匹配 +"*" 或 "" → 通配(匹配所有) +``` + +### if 条件过滤 + +Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`src/utils/hooks.ts`,`prepareIfConditionMatcher` 函数)预编译匹配器: + +```json +{ + "hooks": [{ + "command": "check-git-branch.sh", + "if": "Bash(git push*)" + }] +} +``` + +`if` 条件使用 `permissionRuleValueFromString` 解析,支持与权限规则相同的语法(工具名 + 参数模式)。Bash 工具还会使用 tree-sitter 进行 AST 级别的命令解析。 + +### Hook 去重 + +同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按四部分复合键做 Map 去重:`${pluginRoot}\0${shell}\0${command}\0${ifCondition}`(由 `hookDedupKey()` 函数构建),保留**最后合并的层级**。 + +## 工作区信任检查 + +**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()` 函数,`src/utils/hooks.ts`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。 + +```typescript +// 交互模式下,所有 Hook 要求信任 +const hasTrust = checkHasTrustDialogAccepted() +return !hasTrust +``` + +SDK 非交互模式下信任是隐式的(`getIsNonInteractiveSession()` 为 true 时跳过检查)。 + +## 四种 Hook 能力的源码映射 + +### 1. 拦截操作(PreToolUse) + +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny" + } +} +``` + +`processHookJSONOutput()` 将 `permissionDecision` 映射为 `result.permissionBehavior = 'deny'`,并设置 `blockingError`,阻止工具执行。 + +### 2. 修改行为(updatedInput / updatedMCPToolOutput) + +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "updatedInput": { "command": "npm test -- --bail" } + } +} +``` + +`updatedInput` 替换原始工具输入;`updatedMCPToolOutput`(PostToolUse 事件)替换 MCP 工具的返回值——可用于过滤敏感数据。 + +### 3. 注入上下文(additionalContext / systemMessage) + +- `additionalContext` → 通过 `createAttachmentMessage({ type: 'hook_additional_context' })` 注入为用户消息 +- `systemMessage` → 注入为系统警告,直接显示给用户 + +### 4. 控制流程(continue / stopReason) + +```json +{ "continue": false, "stopReason": "构建失败,停止执行" } +``` + +`continue: false` 设置 `preventContinuation = true`,阻止 Agent 继续执行后续操作。 + +## Session Hook 的生命周期 + +Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(调用位置:`packages/builtin-tools/src/tools/AgentTool/runAgent.ts`;定义位置:`src/utils/hooks/registerFrontmatterHooks.ts`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()`(定义位置:`src/utils/hooks/sessionHooks.ts`)清理。 + +```typescript +// runAgent.ts — 注册 agent 的前置 Hook +registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...) + +// runAgent.ts — finally 块清理 +clearSessionHooks(rootSetAppState, agentId) +``` + +这确保 Agent A 的 Hook 不会泄漏到 Agent B 的执行中。 diff --git a/claude-code-best/docs/features/extensibility/mcp-configuration.md b/claude-code-best/docs/features/extensibility/mcp-configuration.md new file mode 100644 index 0000000..c696096 --- /dev/null +++ b/claude-code-best/docs/features/extensibility/mcp-configuration.md @@ -0,0 +1,346 @@ +--- +title: "MCP 配置 - 多来源合并、作用域与策略管控" +description: "详细说明 Claude Code MCP 配置的来源层次、合并优先级、传输类型、企业策略管控、插件集成和保留名称机制。" +keywords: ["MCP", "配置", "settings.json", ".mcp.json", "企业策略", "插件"] +--- + +## 配置来源与作用域 + +Claude Code 的 MCP 配置来自多个来源,每个来源对应一个 `scope`(作用域)。配置按优先级合并,高优先级来源的同名配置覆盖低优先级。 + +### 来源列表 + +| 来源 | Scope | 文件/接口 | 说明 | +|------|-------|----------|------| +| 企业管控 | `enterprise` | 系统管理路径 `managed-mcp.json` | **排他模式**:存在时忽略所有其他来源 | +| 本地项目 | `local` | `/.claude/settings.local.json` | 项目级私有配置(不提交到 VCS) | +| 项目配置 | `project` | `/.mcp.json` | 项目级共享配置(可提交到 VCS) | +| 用户全局 | `user` | `~/.claude/settings.json` | 用户级配置,所有项目共享 | +| 插件 | `dynamic` | 插件 manifest 中 `.mcp.json` / `.mcpb` | 插件提供的 MCP 服务器 | +| claude.ai | `claudeai` | 通过 API 获取 | claude.ai 网页端配置的连接器 | +| 内置动态 | `dynamic` | 代码中注册 | Computer Use / Chrome 等内置服务器 | +| IDE SDK | `sdk` | IDE 传入 | VS Code / JetBrains 嵌入模式 | + +### 合并优先级(从低到高) + +``` +claude.ai 连接器 ← 最低优先级 + ↓ 去重 +插件服务器 + ↓ 去重 +用户全局配置 + ↓ +项目配置(.mcp.json) ← 需要用户审批 + ↓ +本地项目配置 + ↓ +动态配置(内置 MCP) ← 最高优先级 +``` + +`Object.assign({}, dedupedPluginServers, userServers, approvedProjectServers, localServers)` 实现合并——后出现的同名键覆盖前者。 + +## 企业管控模式 + +当 `managed-mcp.json` 文件存在时,进入 **排他模式**: + +```typescript +// config.ts:1084 +if (doesEnterpriseMcpConfigExist()) { + // 只返回企业配置,忽略所有用户/项目/插件/claude.ai 配置 + return { servers: filtered, errors: [] } +} +``` + +特性: +- 路径由系统管理决定(`getManagedFilePath()` + `managed-mcp.json`) +- 覆盖所有用户级、项目级、插件和 claude.ai 配置 +- 仍然应用策略过滤(allowlist/denylist) +- 无法通过 CLI 添加新服务器(`addMcpConfig` 会拒绝) + +## 传输类型与配置 Schema + +### stdio(默认) + +启动子进程,通过 stdin/stdout JSON-RPC 通信。 + +```json +{ + "my-server": { + "command": "npx", + "args": ["-y", "@my-org/mcp-server"], + "env": { "API_KEY": "..." } + } +} +``` + +`type` 字段可省略(默认为 `stdio`)。环境变量通过 `env` 传递给子进程,会与当前进程环境合并。 + +**Windows 注意**:使用 `npx` 需要包装为 `cmd /c npx`,否则会报错。 + +### SSE(Server-Sent Events) + +通过 HTTP SSE 连接远程 MCP 服务器。 + +```json +{ + "my-remote": { + "type": "sse", + "url": "https://mcp.example.com/sse", + "headers": { "Authorization": "Bearer ..." }, + "oauth": { + "clientId": "...", + "authServerMetadataUrl": "https://auth.example.com/.well-known/oauth-authorization-server" + } + } +} +``` + +支持 OAuth 认证流程。认证失败时进入 `needs-auth` 状态,15 分钟 TTL 缓存避免重复提示。 + +### HTTP(Streamable HTTP) + +HTTP 流式传输。 + +```json +{ + "my-http": { + "type": "http", + "url": "https://mcp.example.com/mcp", + "headers": { "X-API-Key": "..." } + } +} +``` + +支持与 SSE 相同的 OAuth 配置。 + +### WebSocket + +```json +{ + "my-ws": { + "type": "ws", + "url": "wss://mcp.example.com/ws" + } +} +``` + +### IDE 专用类型(内部) + +`sse-ide` 和 `ws-ide` 是 IDE 扩展专用类型,不由用户直接配置。 + +- `sse-ide`:使用 lockfile token 认证 +- `ws-ide`:使用 `X-Claude-Code-Ide-Authorization` header + +### SDK 类型(内部) + +`type: "sdk"` 由 IDE 嵌入模式传入,不经过保留名称检查和企业管控排他限制。 + +### claude.ai 代理类型(内部) + +`type: "claudeai-proxy"` 由 claude.ai 网页端配置的连接器使用,通过 OAuth bearer token 认证并支持 401 重试。 + +## 配置操作 + +### 添加 MCP 服务器 + +通过 CLI 命令 `claude mcp add` 或 API 调用 `addMcpConfig()`: + +```bash +# 添加到用户配置 +claude mcp add my-server -s user -- npx @my-org/mcp-server + +# 添加到项目配置 +claude mcp add my-server -s project -- npx @my-org/mcp-server + +# 添加 HTTP 类型 +claude mcp add my-remote -s user -t http -u https://mcp.example.com/mcp +``` + +添加时的验证流程: + +1. **名称校验**:只允许字母、数字、连字符和下划线 +2. **保留名检查**:`claude-in-chrome` 和 `computer-use` 被保留 +3. **企业管控检查**:企业模式下拒绝添加 +4. **Schema 验证**:Zod 校验配置格式 +5. **策略检查**:denylist 拒绝、allowlist 验证 + +### 移除 MCP 服务器 + +```bash +claude mcp remove my-server -s user +``` + +### 列出 MCP 服务器 + +```bash +claude mcp list +``` + +## 项目配置审批 + +`.mcp.json` 中的项目配置需要用户显式审批才能生效: + +```typescript +// config.ts:1166 +const approvedProjectServers: Record = {} +for (const [name, config] of Object.entries(projectServers)) { + if (getProjectMcpServerStatus(name) === 'approved') { + approvedProjectServers[name] = config + } +} +``` + +首次打开项目时,Claude Code 会提示用户审批 `.mcp.json` 中的每个服务器。审批状态持久化在本地配置中。 + +## 插件 MCP 集成 + +插件通过 manifest 中的 `.mcp.json` 或 `.mcpb` 文件声明 MCP 服务器: + +```typescript +// 插件 MCP 加载流程 +const pluginResult = await loadAllPluginsCacheOnly() +const pluginServerResults = await Promise.all( + pluginResult.enabled.map(plugin => getPluginMcpServers(plugin, mcpErrors)) +) +``` + +### 插件命名空间 + +插件 MCP 服务器名格式为 `plugin::`,不会与手动配置的名称冲突。 + +### 去重机制 + +插件服务器通过内容签名去重(`dedupPluginMcpServers`): + +- **stdio 类型**:签名 = `stdio:` + JSON.stringify([command, ...args]) +- **URL 类型**:签名 = `url:` + 原始 URL(unwrap CCR proxy URL) +- **sdk 类型**:签名为 null,不去重 + +去重规则: +1. 手动配置优先于插件配置 +2. 先加载的插件优先于后加载的 +3. 被抑制的插件服务器在 `/plugin` UI 中显示提示 + +### claude.ai 连接器去重 + +claude.ai 连接器使用相同的内容签名机制去重(`dedupClaudeAiMcpServers`): +- 仅启用的手动配置参与去重(禁用的手动配置不应抑制连接器) +- 连接器名格式为 `claude.ai ` + +## 策略管控 + +### Allowlist / Denylist + +企业策略通过 allowlist 和 denylist 控制可用的 MCP 服务器: + +```typescript +// config.ts:1243 - 最终策略过滤 +for (const [name, serverConfig] of Object.entries(configs)) { + if (!isMcpServerAllowedByPolicy(name, serverConfig)) { + continue // 跳过策略禁止的服务器 + } + filtered[name] = serverConfig +} +``` + +策略检查考虑: +- 服务器名称匹配 +- stdio 类型的 command + args 匹配 +- URL 类型的 URL 模式匹配(支持通配符) + +### 插件专用模式 + +`isRestrictedToPluginOnly('mcp')` 启用时,只允许插件提供的 MCP 服务器——用户/项目级配置被忽略。 + +## 环境变量展开 + +MCP 配置中的环境变量支持 `$VAR` 和 `${VAR}` 语法展开: + +```json +{ + "my-server": { + "command": "npx", + "args": ["@my-org/mcp-server"], + "env": { + "API_KEY": "$MY_API_KEY", + "DB_URL": "${DATABASE_URL}" + } + } +} +``` + +展开时缺失的变量会生成警告信息,但不阻止配置加载。 + +## 内置 MCP 动态注册 + +内置 MCP 服务器在 `main.tsx` 启动流程中动态注入配置: + +### Computer Use MCP + +```typescript +// src/utils/computerUse/setup.ts +export function setupComputerUseMCP(): { + mcpConfig: Record + allowedTools: string[] +} { + return { + mcpConfig: { + "computer-use": { + type: "stdio", + command: process.execPath, + args: ["--computer-use-mcp"], + scope: "dynamic", + } + }, + allowedTools: ["mcp__computer-use__screenshot", ...] + } +} +``` + +启用条件: +- Feature flag `CHICAGO_MCP` 开启 +- `getPlatform() !== "unknown"`(macOS/Windows/Linux) +- 非非交互式会话 +- GrowthBook gate `getChicagoEnabled()` 返回 true + +### Claude in Chrome MCP + +```typescript +// 类似 Computer Use,在 main.tsx 中注册 +const { mcpConfig, allowedTools, systemPrompt } = setupClaudeInChrome() +dynamicMcpConfig = { ...dynamicMcpConfig, ...mcpConfig } +``` + +启用条件: +- `--chrome` 参数或 `claudeInChromeDefaultEnabled` 配置 +- Chrome 扩展已安装 + +### VSCode SDK MCP + +IDE 嵌入模式通过初始化消息传入 `type:'sdk'` 的配置,由 `setupVscodeSdkMcp()` 设置双向通知。 + +## 保留名称 + +以下 MCP 服务器名称被保留,用户无法手动配置同名服务器: + +| 名称 | 用途 | 检查条件 | +|------|------|---------| +| `claude-in-chrome` | Chrome 浏览器控制 | 始终检查 | +| `computer-use` | 桌面自动化 | `CHICAGO_MCP` feature flag 开启时检查 | +| `claude-vscode` | VSCode IDE 集成 | 由 SDK 传入,不经过名称检查 | + +保留名检查在两个位置: +1. `addMcpConfig()`(`config.ts:636-648`)— 运行时拒绝 +2. `main.tsx` 启动检查(`main.tsx:2351-2368`)— 启动时退出 + +## 关键源文件索引 + +| 文件 | 职责 | +|------|------| +| `src/services/mcp/config.ts` | 配置管理核心:合并、去重、策略、添加/删除 | +| `src/services/mcp/types.ts` | Zod Schema 定义、类型声明 | +| `src/services/mcp/client.ts` | 连接管理、传输层选择 | +| `src/utils/plugins/mcpPluginIntegration.ts` | 插件 MCP 配置加载 | +| `src/utils/computerUse/setup.ts` | Computer Use 动态注册 | +| `src/utils/claudeInChrome/common.ts` | Chrome MCP 保留名与工具名 | +| `src/services/mcp/vscodeSdkMcp.ts` | VSCode SDK 双向通知 | diff --git a/claude-code-best/docs/features/extensibility/mcp-protocol.md b/claude-code-best/docs/features/extensibility/mcp-protocol.md new file mode 100644 index 0000000..5498813 --- /dev/null +++ b/claude-code-best/docs/features/extensibility/mcp-protocol.md @@ -0,0 +1,407 @@ +--- +title: "MCP 协议 - 连接管理、工具发现与执行链路" +description: "从源码角度解析 Claude Code 的 MCP 集成:内置 MCP 与外部 MCP 的区别、7 种传输层实现、connectToServer 的 memoize 缓存、工具发现的 LRU 策略、认证状态机、以及 MCP 工具如何进入权限检查链路。" +keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "工具发现", "内置 MCP", "外部 MCP"] +--- + +{/* 本章目标:从源码角度揭示 MCP 客户端的两种运行模式(内置/外部)、连接管理、工具发现协议和执行链路 */} + +## 架构总览:从配置到可用工具 + +``` +配置层(多来源合并) + ├── settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } } ← 外部 + ├── .mcp.json: 项目级 MCP 配置 ← 外部 + ├── 插件 manifest (.mcp.json / .mcpb) ← 外部(插件) + ├── claude.ai connectors ← 外部(远程) + ├── enterprise managed-mcp.json ← 外部(企业管控) + ├── setupComputerUseMCP() / setupClaudeInChrome() ← 内置(动态注册) + └── SDK 传入 (type:'sdk') ← 内置(IDE 嵌入) + ↓ +getAllMcpConfigs() ← enterprise 独占 或 合并 user/project/local + plugin + claude.ai + ↓ +useManageMCPConnections() ← React Hook 管理连接生命周期 + ↓ +connectToServer(name, config) ← memoize 缓存(lodash memoize) + ├── 判断:内置 MCP → InProcessTransport(同进程) + ├── 判断:外部 stdio → StdioClientTransport(子进程) + ├── 判断:远程 SSE/HTTP/WS → 网络传输 + └── 返回 MCPServerConnection ← { connected | failed | needs-auth | pending | disabled } + ↓ +fetchToolsForClient(client) ← LRU(20) 缓存 + ├── client.request({ method: 'tools/list' }) + └── 每个工具包装为 MCPTool ← 统一 Tool 接口 + ↓ +assembleToolPool() ← 合并内置工具 + MCP 工具 + ↓ +工具名格式: mcp____ ← buildMcpToolName() +``` + +## 两种 MCP 模式:内置 vs 外部 + +Claude Code 的 MCP 实现区分 **内置 MCP 服务器** 和 **外部 MCP 服务器**。两者使用相同的客户端协议和工具发现机制,但在连接方式、生命周期管理和配置来源上完全不同。 + +### 内置 MCP 服务器 + +内置 MCP 服务器由 Claude Code 自身提供,无需用户手动配置。它们在启动时自动注册为 `dynamic` scope 的配置,并在同进程内运行。 + +| 服务器 | 名称 | 包路径 | Feature Flag | 启用方式 | +|--------|------|--------|-------------|---------| +| Computer Use | `computer-use` | `@ant/computer-use-mcp` | `CHICAGO_MCP` | GrowthBook gate + macOS + interactive | +| Claude in Chrome | `claude-in-chrome` | `@ant/claude-for-chrome-mcp` | — | `--chrome` 参数或 `claudeInChromeDefaultEnabled` 配置 | +| VSCode SDK | `claude-vscode` | — | — | IDE 嵌入模式 (type:`sdk`) | + +#### InProcessTransport:零开销同进程通信 + +内置服务器通过 `InProcessTransport`(`src/services/mcp/InProcessTransport.ts`)运行,**不启动子进程**: + +```typescript +// 创建一对 linked transport —— 消息在两端之间直接传递 +const [clientTransport, serverTransport] = createLinkedTransportPair() + +// server 端连接到 serverTransport +inProcessServer = createComputerUseMcpServerForCli() +await inProcessServer.connect(serverTransport) + +// client 端使用 clientTransport(与外部 MCP 的 Client 相同接口) +transport = clientTransport +``` + +`InProcessTransport` 的核心设计: +- `send()` 通过 `queueMicrotask()` 异步投递消息到对端,避免同步请求/响应的栈深度问题 +- `close()` 双向关闭,任一端关闭都会触发两端的 `onclose` 回调 +- 无网络开销、无 IPC 序列化、无进程启动时间 + +#### 动态注册流程 + +内置服务器在 `main.tsx` 的启动流程中注册,注入 `dynamicMcpConfig`: + +```typescript +// main.tsx: Computer Use MCP 动态注册 +if (feature("CHICAGO_MCP") && getPlatform() !== "unknown" && !getIsNonInteractiveSession()) { + const { getChicagoEnabled } = await import("src/utils/computerUse/gates.js") + if (getChicagoEnabled()) { + const { setupComputerUseMCP } = await import("src/utils/computerUse/setup.js") + const { mcpConfig, allowedTools } = setupComputerUseMCP() + dynamicMcpConfig = { ...dynamicMcpConfig, ...mcpConfig } + allowedTools.push(...cuTools) + } +} +``` + +`setupComputerUseMCP()` 返回的配置(`src/utils/computerUse/setup.ts`): + +```typescript +{ + "computer-use": { + type: "stdio", // 类型标记为 stdio(但 client.ts 会拦截为 InProcessTransport) + command: process.execPath, + args: ["--computer-use-mcp"], + scope: "dynamic", // 动态作用域,不持久化 + } +} +``` + +#### 连接时拦截 + +`connectToServer()` 在 `client.ts:906-944` 中根据服务器名拦截内置服务器: + +```typescript +// Chrome MCP — 在 process 内运行,避免 ~325MB 子进程 +if (isClaudeInChromeMCPServer(name)) { + const { createChromeContext } = await import('../../utils/claudeInChrome/mcpServer.js') + const { createClaudeForChromeMcpServer } = await import('@ant/claude-for-chrome-mcp') + const { createLinkedTransportPair } = await import('./InProcessTransport.js') + const context = createChromeContext(config.env) + inProcessServer = createClaudeForChromeMcpServer(context) + const [clientTransport, serverTransport] = createLinkedTransportPair() + await inProcessServer.connect(serverTransport) + transport = clientTransport +} + +// Computer Use MCP — 同理 +if (feature('CHICAGO_MCP') && isComputerUseMCPServer(name)) { + const { createComputerUseMcpServerForCli } = await import('../../utils/computerUse/mcpServer.js') + const { createLinkedTransportPair } = await import('./InProcessTransport.js') + inProcessServer = await createComputerUseMcpServerForCli() + const [clientTransport, serverTransport] = createLinkedTransportPair() + await inProcessServer.connect(serverTransport) + transport = clientTransport +} +``` + +#### 保留名称保护 + +内置服务器的名称被保留,用户无法手动添加同名配置(`config.ts:636-648`): + +```typescript +// 添加 MCP 配置时检查保留名 +if (isClaudeInChromeMCPServer(name)) { + throw new Error(`Cannot add MCP server "${name}": this name is reserved.`) +} +if (feature('CHICAGO_MCP') && isComputerUseMCPServer(name)) { + throw new Error(`Cannot add MCP server "${name}": this name is reserved.`) +} +``` + +启动时也有全局检查(`main.tsx:2351-2368`):如果用户配置中包含保留名(非 `type:'sdk'`),直接 `process.exit(1)`。 + +#### VSCode SDK MCP + +VSCode SDK MCP 是特殊的内置模式。IDE(如 VS Code、JetBrains)通过嵌入方式启动 Claude Code,并传入 `type:'sdk'` 的 MCP 配置。这类配置: +- 不经过保留名称检查(IDE 可以使用任意名称) +- 不参与 enterprise MCP 的排他控制 +- 通过 VSCode SDK transport 连接 +- 支持双向通知(如 `file_updated`、`experiment_gates`) + +```typescript +// src/services/mcp/vscodeSdkMcp.ts +export function setupVscodeSdkMcp(sdkClients: MCPServerConnection[]): void { + const client = sdkClients.find(client => client.name === 'claude-vscode') + if (client && client.type === 'connected') { + // 注册 log_event 通知处理器 + client.client.setNotificationHandler(LogEventNotificationSchema(), ...) + // 发送实验门控到 VSCode + client.client.notification({ method: 'experiment_gates', params: { gates } }) + } +} +``` + +### 外部 MCP 服务器 + +外部 MCP 服务器由用户在配置文件中声明,通过子进程或网络连接运行。 + +#### 配置来源 + +| 来源 | Scope | 文件位置 | 优先级 | +|------|-------|---------|--------| +| 项目配置 | `project` | `/.mcp.json` | 最高(同名覆盖) | +| 本地配置 | `local` | `/.claude/settings.local.json` | 高 | +| 用户配置 | `user` | `~/.claude/settings.json` | 中 | +| 插件 | `dynamic` | 插件 manifest 中 `.mcp.json` | 中 | +| claude.ai | `claudeai` | 通过 API 获取 | 低 | +| 企业管控 | `enterprise` | 系统管理路径 `managed-mcp.json` | 排他(存在时覆盖全部) | + +#### 配置示例 + +```json +// settings.json / .mcp.json 中的 MCP 配置 +{ + "mcpServers": { + // stdio 类型 — 启动子进程 + "my-database": { + "command": "npx", + "args": ["@my-org/db-mcp-server"], + "env": { "DB_URL": "postgres://..." } + }, + + // HTTP 流类型 — 远程服务器 + "remote-api": { + "type": "http", + "url": "https://api.example.com/mcp" + }, + + // SSE 类型 — Server-Sent Events + "realtime-feed": { + "type": "sse", + "url": "https://feed.example.com/sse" + }, + + // WebSocket 类型 + "ws-service": { + "type": "ws", + "url": "wss://ws.example.com/mcp" + } + } +} +``` + +#### 配置合并与去重 + +`getAllMcpConfigs()`(`config.ts`)按优先级合并多个来源的配置: + +1. 企业管控配置存在时,**独占返回**(忽略所有其他来源) +2. 否则合并:user → project → local → plugin → claude.ai +3. 插件与手动配置去重:通过 `getMcpServerSignature()` 生成内容签名(基于 command/args/url),插件配置被同名手动配置抑制 +4. `addScopeToServers()` 为每个配置项标注来源 scope + +## 7 种传输层实现 + +`connectToServer()`(`client.ts:596-1643`)根据 `config.type` 分发到不同的 Transport 实现: + +| 传输类型 | Transport 类 | 适用场景 | 认证方式 | +|----------|-------------|---------|---------| +| `stdio`(默认) | `StdioClientTransport` | 外部本地子进程 | 无 | +| `sse` | `SSEClientTransport` | 远程 SSE 服务 | `ClaudeAuthProvider` + OAuth | +| `http` | `StreamableHTTPClientTransport` | HTTP 流 | `ClaudeAuthProvider` + OAuth | +| `sse-ide` | `SSEClientTransport` | IDE 集成 | lockfile token | +| `ws-ide` | `WebSocketTransport` | IDE WebSocket | `X-Claude-Code-Ide-Authorization` | +| `ws` | `WebSocketTransport` | WebSocket 服务 | session ingress token | +| `claudeai-proxy` | `StreamableHTTPClientTransport` | claude.ai 代理 | OAuth bearer + 401 重试 | +| InProcess(内置) | `InProcessTransport` | Computer Use / Chrome | 无(同进程) | + +### stdio 传输的进程管理 + +stdio 类型的 MCP 服务器作为子进程运行,cleanup 时采用 **信号升级策略**(`client.ts:1431-1564`): + +``` +SIGINT (100ms) → SIGTERM (400ms) → SIGKILL +``` + +总清理时间上限 600ms,防止 MCP 服务器关闭阻塞 CLI 退出。 + +### 远程传输的认证状态机 + +SSE/HTTP 类型使用 `ClaudeAuthProvider` 实现 OAuth 认证流程。认证失败时进入 `needs-auth` 状态,并写入 15 分钟 TTL 的缓存文件(`mcp-needs-auth-cache.json`),避免重复弹出认证提示。 + +``` +连接尝试 → 401 Unauthorized + ↓ +handleRemoteAuthFailure() + ├── logEvent('tengu_mcp_server_needs_auth') + ├── setMcpAuthCacheEntry(name) ← 写入 15min TTL 缓存 + └── return { type: 'needs-auth' } ← UI 显示认证提示 +``` + +## 连接缓存与重连机制 + +`connectToServer` 使用 lodash `memoize` 缓存连接对象,缓存 key 为 `${name}-${JSON.stringify(config)}`。 + +### 缓存失效触发 + +当连接关闭时(`client.onclose`),清除所有相关缓存(`client.ts:1376-1404`): + +```typescript +client.onclose = () => { + const key = getServerCacheKey(name, serverRef) + fetchToolsForClient.cache.delete(name) // 工具缓存 + fetchResourcesForClient.cache.delete(name) // 资源缓存 + fetchCommandsForClient.cache.delete(name) // 命令缓存 + connectToServer.cache.delete(key) // 连接缓存 +} +``` + +### 连接降级检测 + +远程传输有 **连续错误计数器**(`client.ts:1229`): + +```typescript +let consecutiveConnectionErrors = 0 +const MAX_ERRORS_BEFORE_RECONNECT = 3 +``` + +遇到终端错误(ECONNRESET、ETIMEDOUT、EPIPE 等)连续 3 次后,主动关闭 transport 触发重连。对于 HTTP 传输,还检测 session 过期(404 + JSON-RPC code -32001)。 + +### 请求级超时保护 + +每个 HTTP 请求使用独立的 `setTimeout` 超时(`wrapFetchWithTimeout`,`client.ts:493`),而非共享 `AbortSignal.timeout()`。原因是 Bun 对 AbortSignal.timeout 的 GC 是惰性的——每个请求约 2.4KB 原生内存,即使请求毫秒级完成也要等 60s 才回收。 + +```typescript +const controller = new AbortController() +const timer = setTimeout(c => c.abort(...), MCP_REQUEST_TIMEOUT_MS, controller) +timer.unref?.() // 不阻止进程退出 +``` + +## 工具发现:从 MCP 到 Tool 接口 + +`fetchToolsForClient()`(`client.ts:1744-2000`)使用 `memoizeWithLRU` 缓存(上限 100),将 MCP 工具转换为 Claude Code 的统一 Tool 接口: + +```typescript +const fullyQualifiedName = buildMcpToolName(client.name, tool.name) +// 结果: "mcp__my-database__query" +``` + +### 内置 MCP 的工具发现 + +内置 MCP 服务器虽然使用 InProcessTransport,但工具发现流程与外部服务器完全一致: + +- **Computer Use**:`createComputerUseMcpServerForCli()` 在 `src/utils/computerUse/mcpServer.ts` 中构建 MCP Server 对象,注册 `ListToolsRequestSchema` handler。工具描述包含平台特定的已安装应用列表(1s 超时枚举)。 +- **Claude in Chrome**:`createClaudeForChromeMcpServer()` 在 `@ant/claude-for-chrome-mcp` 包中构建 Server,提供 17+ 个浏览器控制工具。 +- **VSCode SDK**:由 IDE 端提供工具列表,通过 SDK transport 传递。 + +### 工具描述截断 + +MCP 工具描述上限 2048 字符(`MAX_MCP_DESCRIPTION_LENGTH`)。OpenAPI 生成的 MCP 服务器曾观察到 15-60KB 的描述文档。 + +### 工具能力标注 + +每个 MCP 工具根据 `tool.annotations` 自动标注: + +| 注解 | 映射到 | 含义 | +|------|--------|------| +| `readOnlyHint` | `isReadOnly()` + `isConcurrencySafe()` | 只读,可并行 | +| `destructiveHint` | `isDestructive()` | 破坏性操作 | +| `openWorldHint` | `isOpenWorld()` | 开放世界(不可枚举) | +| `title` | `userFacingName()` | 显示名称 | + +### MCP 工具的权限检查 + +MCP 工具默认返回 `{ behavior: 'passthrough' }`(`client.ts:1816-1834`),意味着它们始终进入权限确认流程。工具名使用 `mcp__` 前缀精确匹配权限规则。 + +内置 MCP 服务器的工具通过 `allowedTools` 列表自动授权——在 `main.tsx` 启动时加入,绕过普通权限提示。例如 Computer Use 工具的 `request_access` 自行处理会话级审批。 + +## MCP 工具的执行链路 + +``` +AI 生成 tool_use: { name: "mcp__my-db__query", input: { sql: "..." } } + ↓ +MCPTool.call() ← client.ts:1835 + ├── ensureConnectedClient() ← 确保连接有效(重连) + ├── callMCPToolWithUrlElicitationRetry() ← 带 Elicitation 重试 + │ ├── client.request({ method: 'tools/call' }) + │ ├── 处理图片结果(resize + persist) + │ └── 内容截断(mcpContentNeedsTruncation) + ├── McpSessionExpiredError → 重试一次 + └── 返回 { data: content, mcpMeta } +``` + +### Session 过期自动重试 + +HTTP 传输的 MCP session 可能过期。检测到 `McpSessionExpiredError` 后自动重试一次(`client.ts:1862`),因为 `ensureConnectedClient()` 已经清除了缓存并建立了新连接。 + +### 内容截断与持久化 + +大型 MCP 工具输出通过 `truncateMcpContentIfNeeded` 截断,二进制内容(图片)通过 `persistBinaryContent` 写入文件并返回文件路径。图片自动 resize(`maybeResizeAndDownsampleImageBuffer`)。 + +## MCP 连接的并发控制 + +```typescript +// 本地服务器并发连接数 +getMcpServerConnectionBatchSize() // 默认 3 + +// 远程服务器并发连接数 +getRemoteMcpServerConnectionBatchSize() // 默认 20 +``` + +本地 MCP 服务器(stdio)是重量级的子进程,默认限制 3 个并发连接。远程服务器是轻量级 HTTP 请求,允许 20 个并发。 + +## 内置 vs 外部 MCP 对比总结 + +| 维度 | 内置 MCP | 外部 MCP | +|------|---------|---------| +| **Transport** | `InProcessTransport`(同进程) | stdio / SSE / HTTP / WebSocket | +| **配置来源** | `setupComputerUseMCP()` / `setupClaudeInChrome()` 等动态注册 | settings.json / .mcp.json / 插件 / claude.ai | +| **Scope** | `dynamic` | `user` / `project` / `local` / `enterprise` / `claudeai` | +| **进程模型** | 同进程,零开销 | 子进程(stdio)或网络连接 | +| **名称保护** | 保留名,用户不可添加同名 | 自由命名(字母数字 + `-_`) | +| **生命周期** | 随 CLI 启停 | 连接缓存 + 按需重连 | +| **权限** | `allowedTools` 自动授权 | `passthrough` 进入权限确认 | +| **Feature Flag** | `CHICAGO_MCP`(Computer Use)等 | 无(始终可用) | +| **工具发现** | 与外部相同(MCP 协议) | 标准 MCP `tools/list` | +| **清理** | `inProcessServer.close()` | 信号升级策略 SIGINT→SIGTERM→SIGKILL | + +## 关键源文件索引 + +| 文件 | 职责 | +|------|------| +| `src/services/mcp/client.ts` | 核心客户端:connectToServer、fetchToolsForClient、MCPTool.call | +| `src/services/mcp/config.ts` | 配置管理:getAllMcpConfigs、addMcpConfig、removeMcpConfig | +| `src/services/mcp/types.ts` | 类型定义:配置 Schema、连接状态类型 | +| `src/services/mcp/InProcessTransport.ts` | 内置 MCP 传输层:linked transport pair | +| `src/services/mcp/vscodeSdkMcp.ts` | VSCode SDK MCP:双向通知、实验门控 | +| `src/services/mcp/useManageMCPConnections.ts` | React Hook:连接生命周期、重连 | +| `src/utils/computerUse/mcpServer.ts` | Computer Use MCP Server 构建 | +| `src/utils/computerUse/setup.ts` | Computer Use 动态注册 | +| `src/utils/claudeInChrome/mcpServer.ts` | Chrome MCP Server 构建 + Bridge 配置 | +| `src/tools/MCPTool/MCPTool.ts` | MCP 工具包装:统一 Tool 接口 | +| `src/entrypoints/mcp.ts` | MCP server 入口(Claude Code 作为 MCP server) | diff --git a/claude-code-best/docs/features/extensibility/skills.md b/claude-code-best/docs/features/extensibility/skills.md new file mode 100644 index 0000000..d19b0b0 --- /dev/null +++ b/claude-code-best/docs/features/extensibility/skills.md @@ -0,0 +1,221 @@ +--- +title: "Skills 技能系统 - Prompt 即能力的架构哲学" +description: "深入剖析 Claude Code Skills 系统的完整实现:从磁盘加载、Frontmatter 解析、预算感知描述截断、双模式执行(inline/fork)、权限白名单、条件激活、动态发现到远程技能加载,揭示一条完整的 Skill 生命周期链路。" +keywords: ["Skills", "SkillTool", "技能加载", "Frontmatter", "whenToUse", "allowedTools", "fork执行", "动态发现"] +--- + +{/* 本章目标:揭示 Skill 系统从文件到执行的全链路实现 */} + +## Tool vs Skill:本质差异 + +| | Tool | Skill | +|---|---|---| +| 粒度 | 单个原子操作(读文件、执行命令) | 一套完整的工作流(代码审查、创建 PR) | +| 触发方式 | AI 自主选择 | 用户 `/skill-name` 或 AI 通过 `SkillTool` 自动匹配 | +| 本质 | TypeScript 执行逻辑 | **Prompt + 权限配置**的声明式封装 | +| 注册位置 | `src/tools.ts` → `getTools()` | `src/commands.ts` → `getCommands()` | +| 执行器 | 各 Tool 的 `call()` 方法 | `SkillTool.call()` → 两条分支(inline / fork) | + +Skill 的核心洞见:**复杂任务的关键不在代码逻辑,而在 Prompt 质量**。一个代码审查 Skill 不需要审查引擎,只需告诉 AI "审查什么、按什么顺序、输出什么格式"——Skill 把这种"经验"封装为可复用的 Markdown。 + +## Skill 的五个来源与加载链路 + +### 1. 内置命令(Built-in Commands) + +硬编码在 `src/commands.ts:299` 的 `COMMANDS` memoize 数组中,包含 70+ 条命令(`/commit`、`/review`、`/compact` 等)。这些是 TypeScript 模块而非 Markdown,但实现了相同的 `Command` 接口(`src/types/command.ts`)。 + +### 2. Bundled Skills(编译时打包) + +通过 `registerBundledSkill()`(`src/skills/bundledSkills.ts:53`)在模块初始化时注册。关键特性: + +- **延迟文件提取**:如果 Skill 声明了 `files`(参考文件),首次调用时才解压到临时目录(`getBundledSkillExtractDir()`),使用 `O_NOFOLLOW | O_EXCL` 防止符号链接攻击(`safeWriteFile`,第 186 行) +- **闭包级 memoize**:并发调用共享同一个 extraction promise,避免竞态写入 +- 来源标记为 `source: 'bundled'`,在 Prompt 预算中享有**不可截断**的特权 + +### 3. 磁盘 Skills(`.claude/skills/`) + +由 `loadSkillsFromSkillsDir()`(`src/skills/loadSkillsDir.ts:407`)加载,这是最重要的加载路径: + +``` +管理策略: $MANAGED_DIR/.claude/skills/ (policySettings) +用户全局: ~/.claude/skills/ (userSettings) +项目级: .claude/skills/ (projectSettings, 向上遍历至 home) +附加目录: --add-dir 指定的路径下 .claude/skills/ +``` + +**加载协议**:只识别 `skill-name/SKILL.md` 目录格式,不再支持单文件 `.md`。加载流程: + +1. `readdir` 扫描目录 → 仅保留 `isDirectory()` 或 `isSymbolicLink()` 的条目 +2. 在每个子目录中查找 `SKILL.md`,未找到则跳过 +3. `parseFrontmatter()` 解析 YAML 头部,提取 `whenToUse`、`allowedTools`、`context` 等字段 +4. `parseSkillFrontmatterFields()`(第 185 行)统一解析 16 个 frontmatter 字段 +5. `createSkillCommand()`(第 270 行)构造 `Command` 对象 + +**去重机制**:使用 `realpath()` 解析符号链接获得规范路径(`getFileIdentity`,第 118 行),避免通过符号链接或重叠父目录导致的重复加载。 + +### 4. MCP Skills(动态发现) + +通过 `registerMCPSkillBuilders()` 注册构建器,MCP Server 的 prompt 被 `mcpSkillBuilders.ts` 转换为 `Command` 对象。标记为 `loadedFrom: 'mcp'`。 + +**安全边界**:MCP Skills 的 Prompt 内容**禁止执行内联 shell 命令**(`loadSkillsDir.ts:374` 的 `loadedFrom !== 'mcp'` 守卫),因为远程内容不可信。 + +### 5. Legacy Commands(`/commands/` 目录) + +向后兼容的旧格式,由 `loadSkillsFromCommandsDir()`(第 566 行)加载。同时支持 `SKILL.md` 目录格式和单 `.md` 文件格式。 + +## Frontmatter 字段全景 + +一个 `SKILL.md` 的完整 frontmatter(`parseSkillFrontmatterFields`,第 185 行): + +```yaml +--- +name: code-review # 显示名称(覆盖目录名) +description: 系统性代码审查 # 描述(或从 Markdown 首段提取) +when_to_use: "用户说审查代码、找 bug" # AI 自动匹配依据 +allowed-tools: # 工具白名单 + - Read + - Grep + - Glob +argument-hint: "" # 参数提示 +arguments: [path] # 声明式参数名(用于 $ARGUMENTS 替换) +model: opus # 模型覆盖 +effort: high # 努力级别 +context: fork # 执行模式:inline(默认)| fork +agent: code-reviewer # 指定 Agent 定义文件 +user-invocable: true # 用户是否可 /调用 +disable-model-invocation: false # 禁止 AI 自主调用 +version: "1.0" # 版本号 +paths: # 条件激活的文件路径模式 + - "src/**/*.ts" +hooks: # Hook 配置 + PreToolUse: + - command: ["echo", "checking"] +shell: ["bash"] # Shell 执行环境 +--- +``` + +解析后有 16 个字段被提取,其中 `allowedTools`、`model`、`effort` 在执行时动态修改 `toolPermissionContext`。 + +## 两条执行路径:Inline vs Fork + +SkillTool(`packages/builtin-tools/src/tools/SkillTool/SkillTool.ts:332`)在 `call()` 中根据 `command.context` 分流: + +### Inline 模式(默认) + +Skill 的 Prompt 内容被注入为 **UserMessage**,在主对话流中继续执行: + +1. `processPromptSlashCommand()` 处理参数替换(`$ARGUMENTS`)和 shell 命令展开(`` !`...` ``) +2. `${CLAUDE_SKILL_DIR}` 被替换为 Skill 所在目录的绝对路径 +3. `${CLAUDE_SESSION_ID}` 被替换为当前会话 ID +4. 返回 `newMessages`(注入到对话流)+ `contextModifier`(修改权限上下文) + +`contextModifier`(第 776 行)做了三件事: +- **工具白名单注入**:将 `allowedTools` 合并到 `alwaysAllowRules.command` +- **模型切换**:`resolveSkillModelOverride()` 处理模型覆盖,保留 `[1m]` 后缀以避免 200K 窗口截断 +- **努力级别覆盖**:修改 `effortValue` + +### Fork 模式(`context: fork`) + +Skill 在**独立子 Agent** 中执行(`executeForkedSkill`,第 122 行): + +1. `prepareForkedCommandContext()` 构建隔离的 Agent 定义和 Prompt +2. `runAgent()` 启动子 Agent 循环,拥有独立的 token 预算 +3. 通过 `onProgress` 回调报告工具使用进度 +4. 结果通过 `extractResultText()` 提取,子 Agent 的全部消息在提取后被释放(`agentMessages.length = 0`) +5. 最终通过 `clearInvokedSkillsForAgent()` 清理状态 + +Fork 模式适用于需要强隔离的场景(如长时间运行的审查任务),避免污染主对话的上下文。 + +## 权限模型:Safe Properties 白名单 + +`checkPermissions()`(第 433 行)实现了一个五层权限检查: + +``` +1. Deny 规则匹配(支持精确匹配和 prefix:* 通配符) + ↓ 未命中 +2. 远程 canonical Skill 自动放行(EXPERIMENTAL_SKILL_SEARCH + USER_TYPE === 'ant') + ↓ 未命中 +3. Allow 规则匹配 + ↓ 未命中 +4. Safe Properties 白名单检查(skillHasOnlySafeProperties,第 911 行) + ↓ 有非安全属性 +5. Ask 用户确认(附带精确匹配和前缀匹配两条建议规则) +``` + +**Safe Properties**(`SAFE_SKILL_PROPERTIES`,第 876 行)是一个包含 30 个属性名的白名单(覆盖 `PromptCommand` 和 `CommandBase` 两个类型的所有安全属性)。任何不在白名单中的**有意义的属性值**(排除 `undefined`、`null`、空数组、空对象)都会触发权限请求。这是**正向安全**设计——未来新增的属性默认需要权限。 + +## Prompt 预算:1% 上下文窗口的截断策略 + +Skill 列表注入 System Prompt 时有严格的字符预算(`prompt.ts`): + +- **预算计算**:`contextWindowTokens × 4 chars/token × 1%`(约 8000 字符) +- **单条上限**:`MAX_LISTING_DESC_CHARS = 250` 字符(超出截断为 `…`) +- **Bundled Skills 不可截断**:它们始终保留完整描述,预算不足时只截断非 bundled 的 +- **降级策略**: + 1. 尝试完整描述 → 超预算? + 2. Bundled 保留完整,非 bundled 均分剩余预算 → 每条描述低于 20 字符? + 3. 非 bundled 仅保留名称 + +`formatCommandsWithinBudget()`(`prompt.ts:70`)实现了这个三级降级。 + +## 动态发现与条件激活 + +### 基于文件路径的动态发现 + +`discoverSkillDirsForPaths()`(`loadSkillsDir.ts:861`)在文件操作时触发: + +1. 从被操作的文件路径开始,**向上遍历**至 CWD(不包含 CWD 本身) +2. 在每层查找 `.claude/skills/` 目录 +3. 使用 `realpath` 去重,`git check-ignore` 过滤 gitignored 目录 +4. 按路径深度排序(**深层优先**),更接近文件的 Skill 优先级更高 + +### 条件激活(paths frontmatter) + +带有 `paths` 模式的 Skill 在加载时不会立即可用,而是存入 `conditionalSkills` Map。当被操作的文件路径匹配某个 Skill 的 paths 模式时(使用 `ignore` 库做 gitignore 风格匹配),该 Skill 才被**激活**——从 `conditionalSkills` 移入 `dynamicSkills`。 + +这意味着一个只在 `*.test.ts` 上激活的测试 Skill,平时完全不可见,只有当 AI 读取或编辑测试文件时才会出现。 + +## 使用频率排名 + +`recordSkillUsage()`(`skillUsageTracking.ts`)使用指数衰减算法计算 Skill 排名分数: + +``` +score = usageCount × max(0.5^(daysSinceUse / 7), 0.1) +``` + +- **7 天半衰期**:一周前的使用权重减半 +- **最低 0.1 保底**:避免老但高频使用的 Skill 完全沉底 +- **60 秒去抖**:同一 Skill 在 1 分钟内的多次调用只计一次,减少文件 I/O + +排名数据持久化在全局配置的 `skillUsage` 字段中。 + +## 远程技能加载(Experimental) + +通过 `EXPERIMENTAL_SKILL_SEARCH` feature flag 控制,支持从远程(AKI/GCS/S3)加载 `_canonical_` 格式的 Skill: + +1. `validateInput()` 中 `stripCanonicalPrefix()` 拦截 canonical 名称 +2. `executeRemoteSkill()`(第 970 行)从远程 URL 加载 SKILL.md +3. 支持 `gs://`、`https://`、`s3://` 等 URL 协议 +4. 内容经过 frontmatter 剥离、`${CLAUDE_SKILL_DIR}` 替换后直接注入 +5. 通过 `addInvokedSkill()` 注册到 compaction 保留状态,确保压缩后仍可恢复 +6. 远程 Skill 不经过 `processPromptSlashCommand`——无 `!command` 替换、无 `$ARGUMENTS` 展开 + +## 完整生命周期总结 + +``` +磁盘 SKILL.md + ↓ parseFrontmatter() + ↓ parseSkillFrontmatterFields() → 16 个字段 + ↓ createSkillCommand() → Command 对象 + ↓ 去重(realpath + seenFileIds) + ↓ 条件 Skill → conditionalSkills Map(等待路径匹配激活) + ↓ getSkillDirCommands() memoize 缓存 + ↓ getAllCommands() 合并 local + MCP + ↓ formatCommandsWithinBudget() → 截断后的 Skill 列表注入 System Prompt + ↓ AI 选择匹配的 Skill + ↓ SkillTool.validateInput() → 名称校验 + 存在性检查 + ↓ SkillTool.checkPermissions() → 五层权限检查 + ↓ SkillTool.call() → inline 或 fork 执行 + ↓ contextModifier() → 注入 allowedTools + model + effort + ↓ recordSkillUsage() → 更新使用频率排名 +``` diff --git a/claude-code-best/docs/features/status-line.mdx b/claude-code-best/docs/features/status-line.md similarity index 100% rename from claude-code-best/docs/features/status-line.mdx rename to claude-code-best/docs/features/status-line.md diff --git a/claude-code-best/docs/internals/ant-only-world.mdx b/claude-code-best/docs/internals/ant-only-world.md similarity index 100% rename from claude-code-best/docs/internals/ant-only-world.mdx rename to claude-code-best/docs/internals/ant-only-world.md diff --git a/claude-code-best/docs/internals/feature-flags.mdx b/claude-code-best/docs/internals/feature-flags.md similarity index 100% rename from claude-code-best/docs/internals/feature-flags.mdx rename to claude-code-best/docs/internals/feature-flags.md diff --git a/claude-code-best/docs/internals/growthbook-ab-testing.mdx b/claude-code-best/docs/internals/growthbook-ab-testing.md similarity index 100% rename from claude-code-best/docs/internals/growthbook-ab-testing.mdx rename to claude-code-best/docs/internals/growthbook-ab-testing.md diff --git a/claude-code-best/docs/internals/growthbook-adapter.mdx b/claude-code-best/docs/internals/growthbook-adapter.md similarity index 100% rename from claude-code-best/docs/internals/growthbook-adapter.mdx rename to claude-code-best/docs/internals/growthbook-adapter.md diff --git a/claude-code-best/docs/internals/hidden-features.mdx b/claude-code-best/docs/internals/hidden-features.md similarity index 100% rename from claude-code-best/docs/internals/hidden-features.mdx rename to claude-code-best/docs/internals/hidden-features.md diff --git a/claude-code-best/docs/internals/sentry-setup.mdx b/claude-code-best/docs/internals/sentry-setup.md similarity index 100% rename from claude-code-best/docs/internals/sentry-setup.mdx rename to claude-code-best/docs/internals/sentry-setup.md diff --git a/claude-code-best/docs/internals/three-tier-gating.mdx b/claude-code-best/docs/internals/three-tier-gating.md similarity index 100% rename from claude-code-best/docs/internals/three-tier-gating.mdx rename to claude-code-best/docs/internals/three-tier-gating.md diff --git a/claude-code-best/docs/introduction/architecture-overview.mdx b/claude-code-best/docs/introduction/architecture-overview.md similarity index 100% rename from claude-code-best/docs/introduction/architecture-overview.mdx rename to claude-code-best/docs/introduction/architecture-overview.md diff --git a/claude-code-best/docs/introduction/what-is-claude-code.mdx b/claude-code-best/docs/introduction/what-is-claude-code.md similarity index 100% rename from claude-code-best/docs/introduction/what-is-claude-code.mdx rename to claude-code-best/docs/introduction/what-is-claude-code.md diff --git a/claude-code-best/docs/introduction/why-this-whitepaper.mdx b/claude-code-best/docs/introduction/why-this-whitepaper.md similarity index 100% rename from claude-code-best/docs/introduction/why-this-whitepaper.mdx rename to claude-code-best/docs/introduction/why-this-whitepaper.md diff --git a/claude-code-best/docs/safety/auto-mode.mdx b/claude-code-best/docs/safety/auto-mode.md similarity index 100% rename from claude-code-best/docs/safety/auto-mode.mdx rename to claude-code-best/docs/safety/auto-mode.md diff --git a/claude-code-best/docs/safety/permission-model.mdx b/claude-code-best/docs/safety/permission-model.md similarity index 100% rename from claude-code-best/docs/safety/permission-model.mdx rename to claude-code-best/docs/safety/permission-model.md diff --git a/claude-code-best/docs/safety/plan-mode.mdx b/claude-code-best/docs/safety/plan-mode.md similarity index 100% rename from claude-code-best/docs/safety/plan-mode.mdx rename to claude-code-best/docs/safety/plan-mode.md diff --git a/claude-code-best/docs/safety/sandbox.mdx b/claude-code-best/docs/safety/sandbox.md similarity index 100% rename from claude-code-best/docs/safety/sandbox.mdx rename to claude-code-best/docs/safety/sandbox.md diff --git a/claude-code-best/docs/safety/why-safety-matters.mdx b/claude-code-best/docs/safety/why-safety-matters.md similarity index 100% rename from claude-code-best/docs/safety/why-safety-matters.mdx rename to claude-code-best/docs/safety/why-safety-matters.md diff --git a/claude-code-best/docs/tools/file-operations.mdx b/claude-code-best/docs/tools/file-operations.md similarity index 100% rename from claude-code-best/docs/tools/file-operations.mdx rename to claude-code-best/docs/tools/file-operations.md diff --git a/claude-code-best/docs/tools/search-and-navigation.mdx b/claude-code-best/docs/tools/search-and-navigation.md similarity index 100% rename from claude-code-best/docs/tools/search-and-navigation.mdx rename to claude-code-best/docs/tools/search-and-navigation.md diff --git a/claude-code-best/docs/tools/shell-execution.mdx b/claude-code-best/docs/tools/shell-execution.md similarity index 100% rename from claude-code-best/docs/tools/shell-execution.mdx rename to claude-code-best/docs/tools/shell-execution.md diff --git a/claude-code-best/docs/tools/task-management.mdx b/claude-code-best/docs/tools/task-management.md similarity index 100% rename from claude-code-best/docs/tools/task-management.mdx rename to claude-code-best/docs/tools/task-management.md diff --git a/claude-code-best/docs/tools/what-are-tools.mdx b/claude-code-best/docs/tools/what-are-tools.md similarity index 100% rename from claude-code-best/docs/tools/what-are-tools.mdx rename to claude-code-best/docs/tools/what-are-tools.md