数据截至 (上游 commit fd0b7e1d9ed9)
Chrome DevTools MCP — 这是什么 / 全景 / 主线 / 阅读地图
30 秒导读: 这是 Google 官方出的一个 MCP server,让你的编码 agent(Claude Code、Cursor、Copilot…)开一台真 Chrome、操作它、并用 DevTools 的全部分析能力检查它。它的独到之处不在"能点按钮",而在给模型的东西是什么形态:页面是一棵带
uid的无障碍树文本、性能问题是一句"LCP 3.2s,瓶颈在文档 延迟"、网络和控制台是分页且可折叠的清单——而不是截图和几万行 JSON。
1. 这是什么(零基础也能懂)
一句话定义
chrome-devtools-mcp 是一个 MCP server(Model Context Protocol,模型上下文协议——一种让 LLM 客户端以统一方式调用外部工具的协议)。它对外暴露约 56 个工具,内部用 Puppeteer 通过 CDP(Chrome DevTools Protocol,Chrome 的远程调试协议)驱动一台真实 Chrome。
解决谁的什么问题
场景:你让 AI 改了一段前端代码,它说"改好了"。但它没看见页面——不知道按钮点下去报没报错、首屏为什么慢、那个请求是不是 404。
于是你要么自己去浏览器里验,要么给它接一个浏览器自动化工具。可普通自动化工具只能"点和抓 HTML",而排障需要的是 DevTools 那一套:性能面板、网络面板、控制台带 source map 的堆栈、内存快照。
这个项目要做的就是把后者整包搬到 agent 面前:
| 你想让 AI 干的事 | 它给的工具 |
|---|---|
| 打开页面、点、填、拖、上传 | new_page / click / fill_form / drag / upload_file |
| 看页面现在长什么样(结构) | take_snapshot(文本无障碍树)、take_screenshot |
| 查报错和网络 | list_console_messages / list_network_requests / get_network_request |
| 查性能 | performance_start_trace / performance_analyze_insight / lighthouse_audit |
| 查内存泄漏 | take_heapsnapshot / compare_heapsnapshots / get_heapsnapshot_retaining_paths |
| 模拟弱网/慢 CPU/移动端 | emulate / resize_page |
工具清单由脚本从真实 schema 生成,见 docs/tool-reference.md(56 个 ### 条目)。
用起来什么样
形态一:MCP server(给 agent 用)。在 MCP 客户端里加一段配置就完事:
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
}
}
}
形态二:CLI(给人/给脚本用)。同一个 npm 包还装了第二个可执行文件 chrome-devtools(package.json 的 bin 字段):
chrome-devtools new_page "https://example.com"
chrome-devtools take_snapshot
chrome-devtools click 1_12 # 1_12 是上一步快照里的 uid
chrome-devtools evaluate_script "() => document.title"
chrome-devtools stop
第一次调用会自动在后台拉起一个常驻进程,后续命令复用同一台浏览器(见 docs/cli.md 与本库第 7 章)。
快照长什么样(理解本项目的关键)
take_snapshot 返回的不是 HTML,而是一棵缩进的无障碍树文本,每行开头带一个 uid:
## Latest page snapshot
uid=1_0 RootWebArea "Example Domain"
uid=1_1 heading "Example Domain" level="1"
uid=1_2 paragraph
uid=1_3 link "More information..." focusable
(格式由 src/formatters/SnapshotFormatter.ts:39 的 #formatNode 决定:两空格缩进 + uid= + role + "name" + 属性。)
然后 agent 说 click(uid="1_3")。"模型指认元素"这件事,靠的是 uid,不是坐标、不是 CSS 选择器。
一句话直觉
可以这样建立心智模型:它把 DevTools 面板里那些给人看的图表,换成了给模型看的短文本结论——面板里那条 LCP 瀑布图,到了这里是一段"LCP 是 3.2 秒,主要花在文档延迟上"的文字。
(这只是入门直觉。下文所有术语都按字面意思用,不再打比方。)
2. 顶层全景(它大概怎么转)
静态结构
怎么读这张图:从上往下是一次请求穿过的层;左右两支是 ToolHandler 分别持有的"状态"和"输出"两个对象。
MCP 客户端(Claude Code / Cursor / Copilot …)
│ stdio,tools/call
▼
┌────────────────────────────────┐
│ ① MCP server 壳 │ src/index.ts
│ 把 56 个工具注册进 SDK │
└───────────────┬────────────────┘
│ 每个工具包一层
▼
┌────────────────────────────────┐
│ ② ToolHandler(一次调用的总控) │ src/ToolHandler.ts
└───┬────────────────────────┬───┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ ③ 状态层 │ │ ④ 输出层 │
│ McpContext / │ │ McpResponse │
│ McpPage │ │ + formatters │
└────────┬────────┘ └──────────────────┘
▼
┌─────────────────────────────────────────┐
│ ⑤ Puppeteer → CDP → 真实 Chrome │
│ ⑥ devtools-frontend(trace/issue/heap) │
└─────────────────────────────────────────┘
部件一句话职责
| 部件 | 干什么 | 主文件 |
|---|---|---|
| MCP server 壳 | 建 McpServer、注册工具、握手时问客户端要 roots | src/index.ts |
| ToolHandler | 一次调用的总控:加锁、备浏览器、跑 handler、渲染、埋点 | src/ToolHandler.ts |
| 工具定义 | 每个工具 = 名字 + 描述 + zod schema + 类别 + handler | src/tools/*.ts、src/tools/ToolDefinition.ts |
| McpContext | 浏览器级状态:页面表、选中页、堆快照、路径沙箱 | src/McpContext.ts |
| McpPage | 页面级状态:快照、对话框、模拟设置、控制台/网络收集器 | src/McpPage.ts |
| McpResponse | 把 handler 打的"标记"统一变成文本 + 结构化内容 | src/McpResponse.ts |
| formatters | 快照/网络/控制台/issue/堆快照各自的文本与 JSON 格式 | src/formatters/*.ts |
| WaitForHelper | 动作之后等导航、等 DOM 不再变 | src/utils/WaitForHelper.ts |
| collectors | 按导航分段地攒控制台消息和网络请求 | src/collectors/PageCollector.ts |
| devtools 复用层 | 直接 import 真 DevTools 前端的 trace 引擎等模块 | src/devtools/*、src/third_party/index.ts |
| CLI + daemon | 把同一批工具变成命令行命令,后台常驻 | src/bin/chrome-devtools.ts、src/daemon/* |
主线:一次 click 从请求到响应
不进代码,先看流程。这条线在第 1、3、4、5 章会被拆开细讲。
客户端 tools/call {name:"click", args:{uid:"1_3"}}
│
├─(1) ToolHandler 拒绝未知参数 → 直接返回可自愈的错误文案
│
├─(2) 抢全局互斥锁 ······ 同一时刻只允许一个工具在跑
│
├─(3) getContext() ······ 第一次调用才真正启动/连接 Chrome
│
├─(4) 校验参数里的文件路径(本工具没有,跳过)
│
├─(5) 取"当前选中页";若有未处理对话框,直接报错
│
├─(6) uid → ElementHandle → Puppeteer Locator.click()
│ └─ 包在 waitForEventsAfterAction 里:等可能的导航 + 等 DOM 静默
│
├─(7) handler 往 response 上打标记(成功文案、要不要附快照)
│
├─(8) McpResponse.handle():并行取快照/网络/控制台…,再统一渲染
│
├─(9) 返回 {content:[text, image?], structuredContent?}
│
└─(10) finally:上报一条脱敏后的遥测,释放锁
这条主线里有四个设计决定,是全项目的骨架:
- 浏览器是懒启动的——server 起来时不开 Chrome,第一次工具调用才开(
src/index.ts:123的getContext)。 - 全局串行——一把
Mutex管住所有工具(src/index.ts:198),不存在两个工具同时操作同一个页面。 - handler 不拼字符串——它只在
response上打标记,渲染集中在一处(第 4 章)。 - 动作自带等待——点击/输入之后要不要等,由
WaitForHelper统一判断(第 5 章)。
3. 阅读地图
按下面顺序读,是一条由浅入深的完整路径:
| 顺序 | 章节 | 讲什么 | 什么时候读 |
|---|---|---|---|
| 1 | 01-tool-definition.md | 工具的定义/注册/开关,ToolHandler.handle 十步 | 想加一个工具、或想懂调用生命周期 |
| 2 | 02-context-and-pages.md | 启动还是接管浏览器、页面 id、路径沙箱 | 关心多标签页、重连、文件安全 |
| 3 | 03-snapshot-and-uid.md | 无障碍树快照与 uid 的跨快照复用 | 关心"模型怎么指认元素" |
| 4 | 04-response-assembly.md | 声明式响应装配、分页、结构化输出 | 关心 token 成本与输出格式 |
| 5 | 05-waiting-and-collectors.md | 自动等待与按导航分段的收集器 | 关心自动化稳定性 |
| 6 | 06-devtools-reuse.md | 复用真 DevTools 的 trace/issue/heap 能力 | 关心性能与内存分析怎么来的 |
| 7 | 07-cli-and-daemon.md | 一份定义生成 MCP 工具 + CLI + 文档,后台 daemon | 关心工程化与多形态分发 |
| 8 | 08-deep-dive-and-boundaries.md | 巧妙之处、边界、横向对比、总代码地图 | 想带走精华 / 做技术选型 |
赶时间的话: 读本页 + 第 3 章 + 第 4 章,就能讲清楚这个项目最独特的那部分(给模型什么形态的页面表示,以及怎么控制输出体积)。
4. 代码地图(入口级)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| MCP server 组装 | src/index.ts | createMcpServer、registerTool、getContext |
| 进程入口(stdio) | src/bin/chrome-devtools-mcp-main.ts | shutdown、StdioServerTransport |
| 一次调用的总控 | src/ToolHandler.ts | ToolHandler、handle、getToolStatusInfo |
| 工具定义原语 | src/tools/ToolDefinition.ts | defineTool、definePageTool、BaseToolDefinition |
| 工具汇总 | src/tools/tools.ts | createTools |
| 浏览器状态 | src/McpContext.ts | McpContext、createPagesSnapshot、validatePath |
| 页面状态 | src/McpPage.ts | McpPage、getElementByUid、emulate |
| 响应装配 | src/McpResponse.ts | McpResponse、handle、format |
| 版本/包信息 | package.json | bin、peerDependencies |