04-想做 Code Agent,但不想只会调 API?我把 Claude Code 源码拆成了一套教程

3 阅读6分钟

关键词:Skills / Plugins / MCP / MiniAgent / 最小实现 / Agent 平台化 / Claude Code

Agent 一旦要长大,就必须插件化:Skills、MCP 和最小实现

如果一个 Agent 的所有能力都写死在主程序里,它很快就会撞墙。

原因很简单:

  • 新功能只能改主仓库;
  • 新工作流只能改主提示;
  • 外部系统接入只能继续硬编码;
  • 能力越来越多,主循环越来越重。

Claude Code 在第七章和第八章做的事,就是把这条路堵死,换成另一条路线:把 Agent 做成平台,把能力做成可插拔。

一、Claude Code 的扩展层不是一层,而是三层

它把扩展拆成:

  • Skills
  • Plugins
  • MCP

这三者经常被混用,但它们负责的事情完全不同。

本质解决的问题
Skills提示词级能力单元把常用动作固化成可复用操作手册
Plugins有生命周期的功能包把 Skills、Hooks、MCP 组合成一个可管理单元
MCP外部工具协议把主程序之外的能力标准化接进来

这个分层非常有价值,因为它让系统不需要只有一种扩展方式。

二、Skill 的本质不是命令,而是“给模型看的工作手册”

一个 Skill 文件本质上就是 Markdown + frontmatter:

---
description: 提交当前改动并生成规范 commit message
allowed-tools: [Bash]
when_to_use: 当用户想提交当前修改时
context: fork
model: sonnet
effort: medium
---

真正关键的字段其实不是 description,而是 when_to_use
因为这个字段不是给人读的,而是给模型判断“什么时候该调这个 Skill”的。

也就是说,Skill 不是文档,而是操作策略。

Claude Code 还会从多个来源发现 Skill:

  • 内置 Bundled Skills;
  • 用户级 Skills;
  • 项目级 Skills;
  • 插件提供的 Skills;
  • MCP 动态提供的 Skills。

这意味着 Skill 已经不是一个目录约定,而是整个系统里的能力注册机制。

三、Plugin 和 Skill 的差别,不在内容,而在生命周期

Plugin 不是“更多 Skill”,而是一个有状态的功能单元。

一个插件定义大概会包含:

  • name / description / version
  • defaultEnabled
  • isAvailable()
  • skills
  • hooks
  • mcpServers

这说明 Plugin 关心的不只是功能,还关心:

  • 默认开不开;
  • 当前环境能不能用;
  • 要不要带后台 MCP 服务器;
  • 生命周期事件要不要挂 Hook。

所以 Plugin 和 Skill 的区别是:

  • Skill 更像一张可复用卡片;
  • Plugin 更像一个功能包。

四、MCP 解决的不是“远程调用”,而是“外部世界怎么标准化接进来”

Claude Code 对 MCP 的实现,是它走向平台化的关键。

MCP 定义的原语主要有三类:

  • Tools
  • Resources
  • Prompts

也就是说,外部系统不只可以暴露工具,还可以暴露:

  • 只读资源;
  • 预定义提示模板。

Claude Code 的客户端会连接外部 MCP Server,然后把远端工具重新注册回本地工具池。

它支持四种传输

import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { WebSocketTransport } from '../../utils/mcpWebSocketTransport.js'

对应四种接入方式:

  • stdio:本地子进程;
  • SSE:远程 HTTP 长连接;
  • Streamable HTTP:新版规范;
  • WebSocket:双向实时通信。
graph LR
    A["Claude Code"] --> B["stdio 本地子进程"]
    A --> C["SSE 远程服务"]
    A --> D["Streamable HTTP"]
    A --> E["WebSocket 服务"]

这说明 Claude Code 没把“工具”理解为进程内对象,而是理解成一种协议终点。

五、MCP 工具怎么变回 Claude Code 工具

连接成功后,Claude Code 会:

  1. list_tools
  2. 拿到远端工具列表
  3. 给每个远端工具造一个本地 MCPTool 实例
  4. 把它们注册进当前工具池

这个本地骨架大概长这样:

export const MCPTool = buildTool({
  isMcp: true,
  name: 'mcp',
  maxResultSizeChars: 100_000,
  get inputSchema(): InputSchema {
    return inputSchema()
  },
  async call() {
    return { data: '' }
  },
  async checkPermissions(): Promise<PermissionResult> {
    return {
      behavior: 'passthrough',
      message: 'MCPTool requires permission.',
    }
  },
})

然后在 client.ts 里覆盖真正的 name / description / call
这是一种典型的模板化接入方式。

六、Claude Code 连 MCP 认证和循环依赖都按平台级问题处理

MCP 一旦接远程服务,认证问题就绕不过去。
Claude Code 专门实现了 OAuth 处理、401 自动刷新、认证失败缓存等逻辑。

还有一个很典型的大项目问题:循环依赖。

MCP Skills 的构建依赖 loadSkillsDir.ts,但 client.ts 又要反过来拿这些构建器。Claude Code 的解法是注册表注入:

let builders: MCPSkillBuilders | null = null

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

export function getMCPSkillBuilders(): MCPSkillBuilders {
  if (!builders) {
    throw new Error('MCP skill builders not registered')
  }
  return builders
}

这其实就是用依赖注入打断模块循环。
很工程化,也很实用。

七、为什么最后一定要回到 MiniAgent

只读源码,最容易出现一种错觉:我懂了。

Claude Code 第八章最重要的价值,是它没有停在分析,而是落回了一个最小可运行实现:MiniAgent。

最小目录结构大概是这样:

mini-agent/
  src/
    index.ts
    types.ts
    tools/
      ReadFileTool.ts
      WriteFileTool.ts
      BashTool.ts
      GrepTool.ts
    ToolRegistry.ts
    ContextManager.ts
    AgentLoop.ts
    CLI.ts

这一步非常关键,因为一旦你自己把它搭一遍,就会立刻理解 Claude Code 前面那些设计为什么不是“过度工程”。

八、MiniAgent 里最值得抄的,不是代码量,而是取舍方式

它没有试图一口气复刻 Claude Code 全部能力,而是把核心骨架缩到最小:

1. Tool 接口先收缩到核心契约

export interface Tool {
  name: string;
  description: string;
  inputSchema: JSONSchema;
  call(input: Record<string, unknown>): Promise<string>;
}

这比 Claude Code 的完整 Tool 协议轻很多,但保留了最关键的三件事:

  • 工具名;
  • 模型可见描述;
  • 结构化输入输出。

2. 工具先挑最有代表性的四个

  • ReadFileTool
  • WriteFileTool
  • BashTool
  • GrepTool

这四个足够覆盖:

  • 文件读取;
  • 文件写入;
  • 命令执行;
  • 代码搜索。

一个 Code Agent 的最小工作集,基本就浓缩在这四类能力里。

3. 连 MiniAgent 也必须做基础安全限制

例如 BashTool 不是裸跑命令,而是先黑名单过滤:

const DANGEROUS_PATTERNS = [
  /rm\s+-rf\s+[\/~]/,
  /:\s*\(\s*\)\s*\{/,
  /dd\s+if=/,
  /mkfs/,
]

ReadFileTool 和 GrepTool 也要控制返回大小,避免一个大文件或一大段 grep 结果直接淹没上下文。

这一步非常说明问题:

就算是最小实现,也不能跳过预算和安全边界。
因为这些不是高级特性,而是 Agent 的基本生存条件。

九、第七章和第八章合起来,本质上是在回答两个问题

问题一:系统怎么继续长

答案是 Skills、Plugins、MCP,把能力做成不同层级的可插拔单元。

问题二:开发者怎么真正学会

答案是把这些结构缩回一个能自己搭起来的最小实现。

如果没有前者,系统会越来越重;
如果没有后者,读者只会停在“看懂别人怎么做”的阶段。

最后

Claude Code 在最后两章里最值得抄的,不是某个具体实现,而是它对“成长路径”的处理:

  • 小能力,用 Skill;
  • 一组能力和生命周期,用 Plugin;
  • 外部世界接入,用 MCP;
  • 真正吸收这些设计,再自己做一个 MiniAgent。

这是一条很完整的路线:

从单体 Agent,到平台化 Agent;
从读懂系统,到重新实现系统。

一个 Code Agent 要想走得远,最后一定得走这条路。