AgentHarness 课程

第六章:MCP 与扩展

7.9K字·20分钟·
MCP 8种传输协议、Hook 20+事件、插件系统

学习时间: 3 小时 | 难度: ⭐⭐⭐ | 前置: 第三章


学习目标

完成本章后,学员将能够:

  • 解释 MCP 客户端的 8 种传输层架构
  • 描述工具发现和调用的完整流程
  • 理解 Hook 系统的 20+ 事件和执行管道
  • 应用 Strategy、Adapter、Event-Driven Middleware 模式

1. MCP 客户端架构

mcp/client.ts (3351行) 实现了 Claude Code 的 MCP (Model Context Protocol) 客户端,支持 8 种传输协议。

1.1 8 种传输层

#传输协议用途实现方式
1Stdio本地子进程通过 stdin/stdout 通信
2SSEServer-Sent EventsHTTP 长连接,服务器推送
3HTTPStreamable HTTPHTTP 请求/响应
4WebSocket全双工通信WS 连接
5SDK 控制传输Anthropic SDK 内部SDK 直接控制
6claude.ai 代理云端代理通过 claude.ai 中转
7SSE-IDEIDE 集成针对 IDE 优化的 SSE
8WS-IDEIDE 集成针对 IDE 优化的 WebSocket

1.2 Strategy Pattern

每种传输协议都是一个独立的 Strategy,实现统一的接口:

// 伪代码:传输策略接口
interface TransportStrategy {
  connect(config: TransportConfig): Promise<Connection>
  send(message: MCPMessage): Promise<void>
  onMessage(handler: (message: MCPMessage) => void): void
  close(): Promise<void>
}

// 具体策略
class StdioTransport implements TransportStrategy { ... }
class SSETransport implements TransportStrategy { ... }
class HTTPTransport implements TransportStrategy { ... }
class WebSocketTransport implements TransportStrategy { ... }

1.3 连接管理

MCP 客户端使用 memoizeWithLRU 缓存连接,结合断路器模式管理连接生命周期:

连接请求
  ↓
检查 LRU 缓存
  ├── 命中且健康 → 返回缓存连接
  └── 未命中或不健康 → 创建新连接
      ↓
      连接成功 → 存入 LRU 缓存
      连接失败 → 错误计数 +1
          ↓
          连续错误 >= 阈值 → 断路器触发
              → 清除缓存
              → 下次请求时重新连接
// 伪代码:连接管理
const memoizedConnect = memoizeWithLRU(
  async (config: MCPConfig) => {
    const transport = createTransport(config.transport)
    const connection = await transport.connect(config)
    
    // 设置断路器
    let errorCount = 0
    connection.on('error', () => {
      errorCount++
      if (errorCount >= CIRCUIT_BREAKER_THRESHOLD) {
        cache.delete(config.key)  // 清除缓存
      }
    })
    
    // 设置自动重连
    connection.on('close', () => {
      cache.delete(config.key)  // 清除缓存,下次自动重连
    })
    
    return connection
  },
  { maxSize: 10, ttl: 'session' }
)

2. 工具发现和调用

2.1 工具发现流程

MCP 连接建立
  ↓
发送 tools/list 请求
  ↓
服务器返回工具列表
  ↓
每个工具转换为 MCPTool
  ├── 继承 Tool 接口
  ├── 设置 mcpInfo (serverName, toolName)
  └── 适配 inputSchema (JSON Schema → Zod)
  ↓
assembleToolPool() 合并到工具池
  → 内置工具在前,MCP 工具在后

2.2 Adapter Pattern (MCPTool)

MCPTool 是一个适配器,将 MCP 协议的工具适配为 Claude Code 的 Tool 接口:

// 伪代码:MCPTool 适配器
class MCPTool implements Tool {
  constructor(
    private mcpClient: MCPConnection,
    private toolDef: MCPToolDefinition
  ) {}
  
  get name() { return this.toolDef.name }
  get inputSchema() { return zodSchema(this.toolDef.inputSchema) }
  
  async call(input: any): Promise<ToolResult> {
    // 适配 MCP 调用协议
    const result = await this.mcpClient.send({
      method: 'tools/call',
      params: { name: this.toolDef.name, arguments: input }
    })
    
    // 适配返回格式
    return {
      output: result.content.map(c => c.text).join('\n'),
      isError: result.isError
    }
  }
  
  // 行为元数据:MCP 工具默认保守
  isConcurrencySafe() { return false }
  isReadOnly() { return false }
}

2.3 Template + Clone Pattern

MCPTool 作为模板,运行时为每个 MCP 工具克隆一个实例:

MCP 服务器返回 5 个工具
  ↓
每个工具创建一个 MCPTool 克隆
  → 共享 mcpClient 连接
  → 独立的 toolDef 配置
  ↓
注入到工具池

3. Hook 系统

hooks/ (~5121行) 实现了 Claude Code 的生命周期钩子系统,支持 20+ 种事件。

3.1 Hook 事件列表

事件触发时机用途
PreToolUse工具执行前输入验证、权限检查
PostToolUse工具执行后结果修改、副作用触发
SessionStart会话开始初始化、环境检查
Stop会话结束清理、统计
PreCompact压缩前保存状态
PostCompact压缩后恢复状态
Notification通知事件用户提醒
SubagentStart子Agent启动上下文注入
SubagentStop子Agent结束结果合并
.........

3.2 Hook 执行管道

事件触发
  ↓
收集所有注册的 Hook
  ↓
按优先级排序
  ↓
顺序执行每个 Hook
  ├── 返回 approve → 继续
  ├── 返回 block → 中断管道
  ├── 返回 systemMessage → 注入系统消息
  └── 抛出异常 → 错误处理
  ↓
所有 Hook 执行完毕
  → 返回最终决策

3.3 Zod 验证

Hook 输出通过 Zod schema 严格验证:

// Hook 输出 schema
const hookOutputSchema = z.object({
  decision: z.enum(['approve', 'block', 'systemMessage']),
  reason: z.string().optional(),
  modifiedInput: z.any().optional(),  // 允许修改工具输入
  systemMessage: z.string().optional(),
})

// 验证 Hook 输出
function validateHookOutput(output: unknown): HookOutput {
  return hookOutputSchema.parse(output)
}

3.4 Event-Driven Middleware Pattern

Hook 系统本质上是一个事件驱动的中间件管道:

请求 → [Hook 1] → [Hook 2] → [Hook 3] → 执行 → 响应
         ↓           ↓           ↓
      可修改输入   可阻止执行   可注入消息

4. 插件系统

plugins/ (~3302行) 实现了 Claude Code 的插件架构。

4.1 插件结构

plugin-name/
├── plugin.json          # 插件元数据
├── commands/            # 自定义命令
│   └── my-command.ts
├── agents/              # 自定义 Agent
│   └── my-agent.ts
└── hooks/               # 自定义 Hook
    └── my-hook.ts

4.2 插件发现

插件来源:
  ├── marketplace (官方插件市场)
  ├── git 仓库 (直接引用)
  └── 本地目录 (开发中)

发现流程:
  1. 扫描配置的插件源
  2. 验证 plugin.json 格式
  3. 检查版本兼容性
  4. 加载并注册插件组件

4.3 Plugin Architecture Pattern

组件接口注册方式
命令PluginCommandcommands.register()
AgentPluginAgentagents.register()
HookPluginHookhooks.register()
工具PluginTooltools.register()

5. 沙箱系统

Claude Code 还包含一个沙箱系统,将权限规则转换为运行时限制:

权限规则
  ↓
转换为 @anthropic-ai/sandbox-runtime 配置
  ├── 文件系统访问控制
  │   ├── 允许读取的目录
  │   ├── 允许写入的目录
  │   └── 禁止访问的目录
  └── 网络访问控制
      ├── 允许的域名
      └── 禁止的域名
  ↓
沙箱运行时强制执行

6. 扩展系统架构图

┌─────────────────────────────────────────────────────────────┐
│                    扩展系统                                   │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ MCP 客户端    │  │ Hook 系统     │  │ 插件系统      │       │
│  │ (3351行)      │  │ (5121行)      │  │ (3302行)      │       │
│  │              │  │              │  │              │       │
│  │ 8种传输协议   │  │ 20+事件       │  │ 命令/Agent/   │       │
│  │ LRU缓存      │  │ Zod验证       │  │ Hook/Tool     │       │
│  │ 断路器        │  │ 中间件管道     │  │ marketplace   │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │               │
│         └─────────────────┼─────────────────┘               │
│                           │                                 │
│                    ┌──────▼───────┐                         │
│                    │ 沙箱系统      │                         │
│                    │ 权限→沙箱配置  │                         │
│                    │ 运行时强制执行  │                         │
│                    └──────────────┘                         │
└─────────────────────────────────────────────────────────────┘

设计模式

本章涉及的模式:

模式定义应用位置
Strategy Pattern统一接口,多种传输实现MCP 8种传输协议
Adapter Pattern协议/格式转换适配器MCPTool 适配器
Event-Driven Middleware事件触发 + 中间件管道Hook 系统
Plugin Architecture标准化接口 + 版本化缓存插件系统
Circuit Breaker连续错误触发断路MCP 连接管理
Template + Clone模板定义骨架,运行时克隆MCPTool 创建

源码验证

  • ✅ MCP 客户端 3351行:client.ts 行数确认
  • ✅ 8 种传输协议:全部在源码中找到对应实现
  • ✅ Hook 系统 ~5121行:hooks/ 目录总行数确认
  • ✅ 插件系统 ~3302行:plugins/ 目录总行数确认
  • ✅ Zod 验证:Hook 输出 schema 确认

思考题

  1. MCP 支持 8 种传输协议,但大多数用户只用 Stdio。为什么要支持这么多传输方式? 这是否增加了维护成本?

  2. Hook 系统的 20+ 事件中,哪些是「必须」的,哪些是「锦上添花」的? 如果要精简到 5 个核心事件,你会选哪些?

  3. MCPTool 的默认行为元数据是 isConcurrencySafe=false, isReadOnly=false(保守策略)。如果 MCP 服务器声明了工具的并发安全性,客户端应该信任吗?

  4. 插件系统的 marketplace 模式需要版本管理和兼容性检查。如果插件 A 依赖插件 B 的特定版本,如何处理依赖冲突?


05-上下文管理 | 07-状态管理与Agent协调