三层架构:Server → Handler → Context → Page
三层架构:Server → Handler → Context → Page
学习目标
本章分析 Chrome DevTools MCP 的四层分离架构。你将理解:
createMcpServer()的职责:McpServer 实例化、浏览器启动、工具注册ToolHandler的职责:参数校验、工具启用检查、Mutex 获取McpContext的职责:全局状态、浏览器实例、事件收集器McpPage的职责:每页状态封装- 延迟初始化模式如何降低启动成本
前置知识
- 工具调用协议与执行模型 — 工具系统分层架构
项目实践
整体架构
Layer 1: createMcpServer() — 入口与注册
src/index.ts 的 createMcpServer() 是整个系统的入口:
createMcpServer(args, options): 1. 初始化遥测(ClearcutLogger) 2. 创建 McpServer 实例 3. 设置 roots/list 请求处理器 4. 定义 getContext() —— 延迟加载浏览器 5. 创建 Mutex(全局工具执行锁) 6. createTools() —— 生成所有工具定义 7. 遍历工具列表,registerTool() 注册到 McpServer 8. 加载 issue descriptions(DevTools 问题描述)延迟初始化模式:
getContext() 是一个懒加载函数,只在首次工具调用时创建 McpContext:
let context: McpContext;async function getContext() { // 根据参数决定连接还是启动 const browser = (hasConnectionParams) ? await ensureBrowserConnected(...) : await ensureBrowserLaunched(...);
// 复用已有 context(browser 相同时) if (context?.browser !== browser) { context = await McpContext.from(browser, logger, opts); } return context;}这种设计避免了 MCP Server 启动时立即打开浏览器的开销。
Layer 2: ToolHandler — 执行管道
ToolHandler 是每次工具调用的执行中枢:
ToolHandler.handle(params): 1. 检查工具是否被禁用(category/condition) 2. 校验未知参数(unknownArgumentNames) 3. 获取 Mutex(确保顺序执行) 4. 获取 McpContext 5. 检测 DevTools 窗口 6. 创建 McpResponse 或 SlimMcpResponse 7. 文件路径校验(verifyFilesSchema) 8. 执行工具 handler 9. 生成响应内容 10. 记录遥测 11. 释放 Mutex(finally 块)Layer 3: McpContext — 全局状态
McpContext 封装了所有全局状态:
| 状态 | 字段 | 说明 |
|---|---|---|
| 浏览器 | browser | Puppeteer Browser 实例 |
| 页面列表 | #pages, #mcpPages | 当前页面快照 |
| 选中页面 | #selectedPage | 当前操作的页面 |
| Isolated contexts | #isolatedContexts | 隔离的 BrowserContext |
| 网络请求 | #networkCollector | 按页面收集请求 |
| 控制台 | #consoleCollector | 按页面收集消息 |
| Trace 结果 | #traceResults | 最近一次 Trace 数据 |
| Roots | #roots | MCP workspace roots |
Layer 4: McpPage — 每页状态
McpPage 封装了每个页面的独立状态:
| 状态 | 字段 | 说明 |
|---|---|---|
| Puppeteer 页面 | pptrPage | 底层 CDP 页面引用 |
| 页面 ID | id | 自增数字标识 |
| 文本快照 | textSnapshot | 无障碍树快照 |
| 模拟设置 | emulationSettings | CPU/网络/视口等模拟状态 |
| Isolated context | isolatedContextName | 所属隔离上下文 |
| DevTools 页面 | devToolsPage | 关联的 DevTools 窗口 |
| 对话框 | #dialog | 当前打开的对话框 |
设计取舍
| 取舍 | 选择 | 原因 |
|---|---|---|
| 单体 Context vs 分层 Context | 分层 | 每个层级职责清晰,易于测试 |
| 每次新建 Context vs 复用 | 复用 | 避免重复初始化事件监听器 |
| 工具注册时检查 vs 执行时检查 | 两者结合 | 注册时决定是否暴露,执行时做参数校验 |
陷阱与对策
| 陷阱 | 后果 | 对策 |
|---|---|---|
| Context 与浏览器断开后仍被使用 | 工具调用全部失败 | getContext() 每次检查 browser 实例 |
| 页面关闭后 McpPage 未被清理 | 内存泄漏 | createPagesSnapshot() 中的 prune 逻辑清理孤儿 |
| Mutex 未释放(异常场景) | 后续工具全部阻塞 | finally 块中 guard.dispose() |