读完 Claude Code 源码才发现:Skills、MCP、Rules 的区别,远没有你想的那么大

张开发
2026/4/18 13:40:47 15 分钟阅读

分享文章

读完 Claude Code 源码才发现:Skills、MCP、Rules 的区别,远没有你想的那么大
导读 introduction通过对Claude Code源码的分析揭示了Rules、MCP、Skills三个概念的底层实现机制。Rules是项目级行为规范通过messages被动注入MCP是标准化工具协议在system和tools中注册并调用外部服务Skills是可复用提示词通过tool_use触发后注入指令文本。三者的核心区别在于信息在API请求中的位置不同而非功能本质…01 背景1.1 概念爆炸学不完的新名词如果你在用 Claude Code、Cursor 或其他 Coding Agent你一定经历过这样的感受——刚弄明白怎么写 Rules 让 Agent 听话MCP 就火了一堆人说MCP 才是未来MCP 的 Server 还没配明白Skills 又冒出来了号称标准化工作流。每隔几周就有新概念冒出来配上各种似是而非的定义让人焦虑这些东西之间到底是什么关系我是不是又落伍了1.2 越看越糊涂的官方定义网络上流行的定义往往加剧了混乱“Rules 是项目级的行为规范”“MCP 是标准化的工具协议”“Skills 是可复用的标准化工作流”看完这些你可能和我一样产生了更多疑问Rules vs Skills都说 Skills 的优势是按需引入但.claude/rules/里的条件规则不也能按路径按需生效吗它们的区别到底在哪MCP vs 内置 ToolsMCP 工具和 Claude Code 自带的 Read、Edit、Bash 这些内置工具对模型来说有什么不同为什么需要一套新协议Skills 的标准化流程所谓流程化是真的像代码一样有 if-else 和循环控制的还是只是一段写得好的提示词1.3 从源码找答案这些问题靠读文档和博客是答不清楚的因为它们本质上是实现层面的问题。恰好 Claude Code 的源码在 GitHub 上泄漏本文基于v2.1.88 泄漏源码从 LLM API 调用层面拆解 Rules、MCP、Skills 的底层实现。看完源码你会发现这三个概念远没有网上说的那么玄乎它们的区别本质上就是信息在 API 请求中被塞到了不同的位置。02 前置需要了解的为了避免阅读本文的读者对一些 Agent 中的流程不够了解先介绍一下相对重要的知识点。2.1 Agent 与 LLM API 的交互协议每次 Agent 调用 LLM本质上就是发一个 HTTP 请求请求体由三个核心参数组成anthropic.messages.create({ system: TextBlockParam[], // 静态角色定义和行为规范 tools: BetaToolUnion[], // 工具定义name description input_schema messages: MessageParam[], // 动态对话内容 })2.1.1 system — “你是谁你该怎么做”定义模型的角色和行为规范。在 Claude Code 中system 包含核心系统提示行为规范、编码风格、安全规则等Git 状态信息通过appendSystemContext追加MCP Server 级 instructions若 Server 提供了使用说明追加在动态区域中system 提示分为静态部分可跨用户缓存和动态部分因会话而异不参与缓存共享。MCP instructions 属于动态部分。system 的静态部分高度稳定可利用 Anthropic 的 org 级 Prompt Cache。同一份静态内容只需计算一次 KV 矩阵所有用户共享缓存后续调用仅需 0.1x 费用。CLAUDE.md 等因项目而异的内容不放在 system 里就是为了不破坏这份共享缓存。2.1.2 tools — “你能做什么”tools数组定义模型可以调用的工具。每个工具包含name、description来自工具的prompt()方法输出、input_schema。模型根据工具描述决定何时调用哪个工具。内置工具和 MCP 工具在这里的格式完全一致模型无法区分它们——区别只在 Agent 侧的执行路由。2.1.3 messages — “对话发生了什么”messages是一个 user/assistant 交替的消息数组但在 Claude Code 中它远不只是对话历史。实际混合了三种内容系统上下文注入prependUserContextCLAUDE.md 内容、当前日期等系统提示上下文appendSystemContextgit 状态等注入到 system 参数动态附件AttachmentsSkill 列表、计划模式指令、子目录 CLAUDE.md 等真实对话历史用户输入、模型回复、工具调用结果前两类都以isHidden: trueisMeta: true注入用system标签包裹。isHidden是客户端侧的 UI 标记消息仍完整发送给 API但不会在终端界面中展示给用户。system不是 API 特殊字段而是 Claude Code 与模型之间的约定格式系统提示词中会告知模型被此标签包裹的内容权重等同于系统指令让模型能区分系统注入的指令和用户真正说的话。为什么系统上下文不放在system里因为 CLAUDE.md 等内容因项目而异混入 system 会破坏 org 级共享缓存。放在 messages 中既不影响 system 缓存又能在会话内轮次间复用。2.2 tool_use一切扩展机制的底层基础Claude 的工具调用本质上是一个结构化的多轮对话协议用户消息 ↓ 模型推理 → 输出 tool_use 块 { type: tool_use, id: toolu_xxx, name: 工具名, input: { ...参数... } } ↓ 调用方Agent执行工具 ↓ 将结果作为 tool_result 追加到对话 { type: tool_result, tool_use_id: toolu_xxx, content: 执行结果 } ↓ 继续下一轮模型推理模型本身不执行任何工具它只是输出一段结构化 JSON真正的执行发生在调用方即 Claude Code 客户端。理解了这一点就能理解为什么 Rules、MCP、Skills 虽然表现形式完全不同但底层都构建在同一套tool_use协议之上。03 实现细节3.1 RulesCLAUDE.md的实现3.1.1 Rules 是什么Rules 就是CLAUDE.md 文件以及.claude/rules/*.md规则文件。它们是用自然语言写的指令文本告诉模型在这个项目中你应该遵循什么规范。3.1.2 文件发现机制Claude Code 从多个位置按优先级加载 Rules源码中对应getMemoryFiles函数。实际加载逻辑是从项目根到 CWD 逐层处理每层内部按CLAUDE.md→.claude/CLAUDE.md→.claude/rules/*.md→CLAUDE.local.md的顺序收集后加载的覆盖先加载的。主要来源包括单个 CLAUDE.md 文件建议不超过40,000 字符超出会触发诊断警告⚠️。3.1.3 内容处理流程每个文件经过processMemoryFile处理读取文件 ↓ 解析 frontmatter提取 paths 等条件匹配字段 ↓ 移除 HTML 注释 ↓ 处理 include 引用最大递归深度 5 层 ↓ 条件规则匹配.claude/rules/*.md 中 paths 字段匹配当前文件路径 ↓ 格式化输出条件规则是一个值得注意的特性。在.claude/rules/下的规则文件可以通过 frontmatter 中的paths字段指定生效范围--- paths: - src/components/**/*.tsx - src/hooks/**/*.ts --- 在 React 组件中始终使用函数式组件和 hooks。这意味着这条规则只在模型处理匹配路径的文件时才会被注入。3.1.4 注入方式进入 messages而非 system格式化后的 Rules 内容通过prependUserContext()注入到messages 的最前面包裹在system-reminder标签中以role: userisMeta: true的形式存在——isMeta是客户端 UI 标记消息本身仍完整发送给 API但不会在终端中展示给用户。注入时还会带上一个强制指令头“Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.”核心洞察Rules 不走****tool_use****协议。它既不是工具也不需要模型主动调用。它是被动注入到每次 API 调用的上下文中模型在推理时自然会看到并遵循这些规则。具体的prependUserContext()源码还原见 [[Claude Code 架构解析从 Skill 调用到 Prompt Cache]]3.1.5 子目录 Rules 的动态加载当模型在对话过程中访问了某个子目录的文件Claude Code 会检查该子目录是否有 CLAUDE.md。如果有会通过nested_memoryattachment 动态注入// nested_memory attachment 处理 case nested_memory: return [createMessage({ content: Contents of ${attachment.content.path}:\n\n${attachment.content.content}, isMeta: true })];这实现了 Rules 的按需加载——只有当模型实际接触到某个子目录时该目录的规则才会被加载进来。3.2 MCP Tools 的实现3.2.1 MCP 是什么MCPModel Context Protocol是一个标准化协议让 Claude Code 能调用外部服务提供的工具。它是tool_use最直接的应用——模型触发后客户端向外部 MCP Server 进程发起 RPC 调用拿到真实结果。3.2.2 配置与连接MCP 服务器定义在~/.claude.jsonuser scope或项目根目录的.mcp.jsonproject scope中常见传输方式连接建立后Claude Code 通过 MCP SDK 与 Server 完成initialize握手。这一步不仅获取工具列表还会拿到 Server 返回的instructions 字段——一个可选的 Server 级使用说明后面会看到它的去向。3.2.3 MCP 在 API 请求中占据两个位置MCP 不只是注册在tools[]里它还在system中有一席之地。位置一tools[] — 工具定义每个 MCP 工具通过toolToAPISchema()转换为tools[]格式命名遵循mcp__serverName__toolName模式// toolToAPISchema 核心逻辑 async function toolToAPISchema(tool, options) { return { name: tool.name, // 如 mcp__github__create_issue description: await tool.prompt(), // 工具描述 → tools[].description input_schema: tool.inputJSONSchema // 参数 schema }; }这部分和内置工具的注册方式完全一致模型通过工具描述决定何时调用。位置二system — Server 级 instructions在系统提示词的构建过程中getMcpInstructions()会将所有已连接 Server 的 instructions 拼接进 system 的动态区域位于缓存边界标记之后// getMcpInstructions源码路径src/constants/prompts.ts function getMcpInstructions(mcpClients) { const clientsWithInstructions mcpClients .filter(c c.type connected) .filter(c c.instructions); // 只取有 instructions 的 Server if (clientsWithInstructions.length 0) return null; return # MCP Server Instructions The following MCP servers have provided instructions for how to use their tools and resources: ${clientsWithInstructions.map(c ## ${c.name}\n${c.instructions}).join(\n\n)} ; }当 feature gateisMcpInstructionsDeltaEnabled()开启时MCP instructions 会改走 attachment 注入而非 system以避免 Server 连接/断开破坏 prompt 缓存。MCP Server 可以通过initialize响应的instructions字段向模型传达整个 Server 级别的使用指南比如优先使用 search 工具而非 list 工具、所有日期参数必须用 ISO 格式等。这些指导信息是全局性的不是针对单个工具的。tools[].description描述的是这个工具做什么、参数是什么system中的 instructions 描述的是如何正确地使用这个 Server 的工具集。一个是单工具说明书一个是整体使用手册。3.2.4 执行流程MCP 工具的调用是真正的函数调用模型输出 tool_use: { name: mcp__github__create_issue, input: {...} } ↓ Claude Code 识别 mcp__ 前缀路由到对应 MCP Client ↓ MCP Client 发送 JSON-RPC 请求到 MCP Server 进程 ↓ MCP Server 执行实际操作如调用 GitHub API ↓ 返回真实结果 ↓ tool_result.content MCP Server 的真实输出 ↓ 模型读取结果继续推理MCP 是名副其实的远程过程调用。工具做真实的事情结果回传给模型。tool_result里装的是外部世界的真实数据。3.2.5 MCP 祛魅很多场景下一条 Bash 就够了理解了源码实现后一个自然的问题浮出水面模型已经有 Bash 工具了为什么还需要 MCP对模型来说调mcp__github__list_issues和执行gh issue list拿到的结果没有本质区别——都是tool_result里的一段文本。但 MCP 多了一个 Server 进程、一层 JSON-RPC 通信、一套配置和维护成本。实际使用中查 GitHub 用gh读数据库用psql调 API 用curl大量 MCP Server 做的事一条命令就能替代。那 MCP真正不可替代的场景是什么持久化连接和状态管理Bash 每次是新进程没有状态。数据库连接池、WebSocket 长连接、跨调用共享认证 sessionMCP Server 作为常驻进程可以做到复杂操作的原子封装把 5 步 Bash 命令封装成一次 MCP 调用减少模型拼长命令出错的概率权限隔离和安全约束Bash “什么都能干”MCP Server 可以限制模型只执行预定义操作MCP 的价值不在于能调用外部系统Bash 也能而在于以更安全、更可靠的方式调用外部系统。3.3 Skills 的实现3.3.1 Skills 是什么Skills 是可复用的 Markdown 提示词文件SKILL.md定义了一套结构化的工作指令。它同样通过tool_use触发但执行逻辑与 MCP 截然不同。3.3.2 文件发现Skills 从以下位置扫描发现3.3.3 Skill 列表注入模型怎么知道有哪些 Skill 可用通过skill_listingattachment 注入到 messages 中// skill_listing attachment 处理 case skill_listing: { return [createMessage({ content: The following skills are available for use with the Skill tool:\n\n${attachment.content}, isMeta: true })]; }Skill 列表有严格的 token 预算仅占上下文窗口的1%默认 8000 字符每个 Skill 描述最多 250 字符。当 Skill 数量过多时描述会被截断甚至移除。这是为了避免 Skill 列表挤占对话空间。同时Skill 工具的description中包含一条强制触发指令BLOCKING REQUIREMENT“When a skill matches the user’s request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task”这条指令确保模型看到匹配的 Skill 时必须先调用工具不能直接回答。3.3.4 执行流程提示词注入不是函数调用当模型调用 Skill 工具时默认走Inline 模式模型输出 tool_use: { name: Skill, input: { skill: commit, args: } } ↓ Claude Code 读取本地 SKILL.md 提示词文本 ↓ 将提示词内容包装为 isMeta: true 的 user 消息注入到对话历史中 ↓ tool_result 仅返回一个标签Launching skill: commit ↓ 下一轮 API 调用时对话历史中已包含完整的 Skill 指令 ↓ 模型读到指令后按步骤调用工具Read、Edit、Bash 等执行任务3.3.5 Inline 模式 vs Fork 模式Skills 有两种执行模式Inline 是默认模式Fork 需要 Skill 配置文件中显式设置context: fork才会触发Fork 的隔离性意味着 Skill 内部的文件缓存、权限拒绝记录、abort 控制都是独立的不会污染主对话上下文。核心洞察Skills 是提示词注入机制不是函数调用。tool_use只是触发器真正的能力来自被注入的 Markdown 指令文本。模型读到指令后按指令一步步执行利用已有的工具Read、Edit、Bash 等完成任务。04 总结4.1 三者的核心对比4.2 一张图理解全貌4.3 回答开头的三个问题Q1Rules 和 Skills 都支持按需引入区别在哪先说结论区别没有想象中大。从源码看Skills 执行后注入的就是一段 Markdown 提示词和你手动把一段 Rules 文本贴进对话框对模型来说没有本质区别——都是 messages 里的一段role: user文本。真正的区别只有两点工程实现上的差异触发方式Rules 每次 API 调用自动注入Skills 需要模型判断后主动调用tool_use或用户手动/skill-name触发执行隔离Skills 可配置在 Fork 上下文中运行拥有独立的缓存、权限跟踪和 abort 控制Rules 没有这层隔离但现实中第一点反而成了 Skills 的痛点。模型判断是否需要调用 Skill依赖的是skill_listing中最多 250 字符的描述加上whenToUse字段——这点信息经常不够模型做出正确判断。这就是为什么很多人发现 LLM 不会自动触发 Skill最终还是靠手动/commit、/review-pr来调用。想想这意味着什么如果你每次都是手动触发那 Skills 的完整调用链路是这样的你输入 /commit → Claude Code 查找对应 SKILL.md → 包装为 tool_use 调用 → 读取 Markdown 文本 → 注入到 messages 中 → 模型读到这段文本按指令执行而你手动用commit-rules.md引用一个同等内容的 Rules 文件效果是你输入 commit-rules.md 帮我提交代码 → Claude Code 读取文件内容 → 作为 FileAttachment 注入到 messages 中 → 模型读到这段文本按规范执行两者最终模型看到的都是一段自然语言指令没有本质区别。Skills 多绕的那几步tool_use→ 读文件 → 注入本质上只是提供了额外的工程便利。如果你每次都是手动/commit那和直接commit-rules.md效果几乎一样。那 Skills 真正有价值的场景是什么关键在于手动引用 Rules 替代不了的三个点1. 模型自主触发——用户只需表达意图当 Skill 的description/whenToUse写得足够精准模型能自动识别场景并触发用户不需要知道这个 Skill 的存在。差距在单步场景不明显但在多步骤组合任务时就凸显了用户帮我完成这个 feature包括写代码、写测试、提交 手动引用方式 coding-rules.md test-rules.md commit-rules.md → 用户需要知道有哪些规则、叫什么名字、在哪里 Skill 自动触发 → 模型识别任务依次自动调用 coding / test / commit skill → 用户只说了目标工具选择完全交给模型Skills 还支持嵌套调用Skill 内部再触发其他 Skill可以用一个主 Skill编排多个子 Skill形成完整的多步工作流入口。注意自动触发能力的上限取决于 Skill 描述的质量而不是 Skill 数量。描述模糊或触发时机不清晰的 Skill模型大概率不会自动识别最终还是要靠手动/skill-name触发——此时就和手动引用 Rules 没有区别了。2. 可发现、可分发——团队协作的标准化入口Skill 有名字、注册在系统里可以通过/skills浏览可以打包进插件发布给团队。Rules 文件路径是私人知识Skill 是被组织管理的知识。当你需要把一套工作流标准化并推广给不了解内部实现的团队成员时Skill 是更合适的载体——用户只需记住/commit不需要知道背后引用了哪些规则文件。3. Fork 模式的独立执行生命周期——这是手动引用 Rules 做不到的配置context: fork后Skill 在独立 Agent 上下文中运行执行过程中所有的 tool_use/tool_result 不会写入主对话主对话保持干净有独立的 abort 控制和权限跟踪不会影响主流程。长流程多步任务特别适合 Fork 模式。Q2MCP 和 LLM 内置 Tools 的区别在哪对模型来说没有区别。tools[]里格式一样调用方式一样。区别纯粹在 Agent 侧的执行路由内置 Tools 本地执行MCP Tools 转发到外部 Server。如上文 2.5 所述大多数简单场景 Bash 就能替代 MCP。MCP 真正的价值在持久化连接、原子封装和权限隔离三个点上。另外 MCP 的 Server 级instructions注入到system中理论上能提供工具集使用指南但现实中大多数 Server 作者根本没写这个可选字段。Q3Skills 的标准化流程是代码层面的流程化吗不是。源码里没有任何代码逻辑来控制 Skill 的执行步骤。所谓标准化工作流就是一段写得比较结构化的 Markdown——“Step 1 做什么Step 2 做什么”。模型读到后自行理解、自行执行完全靠模型的指令遵循能力。这意味着Skill 的质量 提示词的质量Skill 的流程保障 模型的指令遵循率同一个 Skill换一个弱一点的模型流程可能就乱了从这个角度看写一个好的 Skill 和写一段好的 Rules需要的能力是一样的——都是提示词工程。4.4 实际使用建议基于源码分析和实际使用经验给出一些落地建议什么时候用 Rules项目级的编码规范、技术栈约定、代码风格要求文本短几百字以内每次注入不心疼 token需要始终生效的指令不依赖模型判断是否需要什么时候用 Skills指令文本较长几百行级别不适合每次注入有明确的触发时机用户主动/commit、/review-pr需要执行隔离Fork 模式能让任务在独立上下文中运行不污染主对话什么时候用 MCP需要持久化连接/状态管理的场景数据库连接池、认证 session复杂多步操作需要原子封装减少模型拼命令出错的概率需要权限隔离不想给模型一个万能的 Bash如果只是简单的 CLI 操作gh、curl、psql直接让模型用 Bash别折腾 MCP一个现实提醒不要迷信 Skills 的自动触发。源码中 Skill 列表的 token 预算只有上下文的 1%每个描述最多 250 字符。如果你的 Skill 描述写得不够精准或者用户意图不够明确模型大概率不会自动触发。把核心 Skill 的快捷命令告诉团队成员让他们手动调用比指望模型自动识别靠谱得多。MCP 同理——在引入之前先想想Bash 能不能直接搞定。参考源码claude-code-source-codev2.1.88泄漏源码https://github.com/anthropics/claude-code

更多文章