跳到主要内容

数据截至 (上游 commit fd0b7e1d9ed9)

第 8 章:巧妙之处、边界与横向对比

这章讲什么: 把前七章散落的设计决策收成一张可带走的清单,再诚实地说清它做不到什么、以及和同货架兄弟项目的取舍差异。


8.1 巧妙之处(可借鉴的技术)

① uid 跨快照复用:让引用不因重新截图而作废

妙在哪: 大多数"元素引用"方案在重新截取页面表示后就失效,模型必须重来一遍。这里用 loaderId + backendNodeId 做索引,同一个 DOM 节点在多次快照之间保持同一个 uid;文档一换(loaderId 变),旧 uid 自动全部失效。

依据: src/TextSnapshot.ts:73-87assignIds,复用表在 src/McpPage.ts:119uniqueBackendNodeIdToMcpId

能拿走的模式: 对外暴露短 id,对内用一个稳定的物理键做索引,并在"世代"变化时整批作废。


② 响应装配集中化:handler 不生产文本

妙在哪: 56 个工具的 handler 只调 setter 打标记,渲染逻辑集中在 format() 一处。于是分页、脱敏、压缩编码、结构化输出这些横切能力只写一遍就全局生效,而且所有工具的输出结构天然一致。

依据: src/McpResponse.ts:735format,对照 src/tools/input.ts:121-129 的 handler(全部输出就三行 setter)。


③ 错误自带修复路径

妙在哪: 每一条面向模型的错误都回答"接下来该做什么",而不只是"哪里错了"。

场景错误文案的可操作部分依据
传了未知参数列出期望参数 + "Remove them and retry"src/ToolHandler.ts:138
工具被类别禁用给出确切的开启命令src/ToolHandler.ts:35
还没截过快照"Use take_snapshot to capture one"src/McpPage.ts:642
分页越界自动回第一页 + 提示src/McpResponse.ts:1420-1422
浏览器已在运行"Use --isolated to run multiple instances"src/browser.ts:268-271
insight 名字错"Only use ids given in the 'Available insight sets' list"src/processors/PerformanceTrace.ts:115-118

并且工具名是从定义里取的(listPages().namehandleDialog.nametakeSnapshot.name),改名不会让提示失真。


④ handler 抛错也照样给上下文

妙在哪: 内层 catch 把 handler 的异常存进 response.setError,流程继续走完渲染。模型拿到的是"点击失败 + 当前页面结构 + 控制台里那条报错",一次往返就能判断怎么改。

依据: src/ToolHandler.ts:340-342,错误最终渲染在 src/McpResponse.ts:1395-1398


⑤ schema 驱动的遥测脱敏

妙在哪: 遥测不是把参数直接上报,而是按 zod 类型逐个变形:

ZodString → 只报长度,且长度还要过一遍分桶 name → name_length: 50
ZodArray → 只报元素个数 filePaths → file_paths_count: 3
ZodNumber/Boolean/Enum → 原样(本身无隐私)
uid / reqid / msgid → 直接不报(PARAM_BLOCKLIST)
延迟 → 按 [50,100,250,500,1000,…] 分桶
URL → 只报 is_localhost 布尔

类型不匹配时直接抛错(src/telemetry/transformation.ts:178-182)而不是静默上报——宁可掉一条埋点也不冒泄漏风险。

依据: sanitizeParams(:168)、PARAM_BLOCKLIST(:24)、bucketizeLatency(:15)、buildContext(:201)。

能拿走的模式: 让 schema 同时承担"参数校验"和"遥测脱敏规则"两个职责,新增参数时脱敏自动跟上,不会漏配。


⑥ 超标自动改变策略

妙在哪: 截图超过 2MB 时,即使模型没传 filePath,也自动写临时文件并只回路径(src/tools/screenshot.ts:275-280)。不依赖调用方记得优化,由被调方兜底。

同源的还有:标题获取 1 秒熔断(src/McpResponse.ts:1463)、getDevToolsData 500ms 熔断(src/McpContext.ts:436)、堆栈 source map 1 秒上限(src/devtools/DevtoolsUtils.ts:423)。


⑦ 把危险的等待都限时

妙在哪: 项目里几乎每个可能挂住的地方都套了 race。最典型的是 waitForStableDom 连"注入观察者"这一步都限时,注释直接点出后果:一个 alert() 暂停渲染,会让 evaluateHandle 挂到 180 秒协议超时,而此时工具互斥锁还握着

依据: src/utils/WaitForHelper.ts:46-75

能拿走的模式: 只要在持锁期间做 I/O,就必须给这段 I/O 一个明确的上界。


⑧ 复用而非重写

性能诊断、issue 分类、堆快照分析、网络头脱敏,全部直接调 DevTools 前端的实现(第 6 章)。结论口径与用户在浏览器里看到的一致,这本身就是产品价值。


8.2 边界与局限

明确不做的事

限制依据影响
只官方支持 Chrome / Chrome for TestingREADME.md 免责声明其他 Chromium 系可能能用但不保证
全局串行,零并发src/index.ts:198 单个 toolMutex多个 agent 共用一个 server 时会互相排队
进程内只有一台浏览器src/browser.ts:21 模块级单例要多实例只能起多个 server 进程
只保留最新一条 tracesrc/McpContext.ts:700-704 先清空再 push无法对比两次性能录制
每个页面最多 3 段历史数据PageCollector.maxNavigationSaved = 3更早的控制台/网络记录已丢弃
每次导航最多 1000 条网络请求NetworkCollector.MAX_REQUESTS_PER_NAVIGATION超出从最早的开始丢
响应体截断到 10000 字符NetworkFormatterBODY_CONTEXT_SIZE_LIMIT大响应体要用 filePath 落盘
CLI 不支持复杂参数scripts/generate-cli.ts 遇非字符串 type 直接抛错fill_form 等在 CLI 里用不了

会在哪里崩或表现不佳

  • 无障碍树里没有的元素,agent 就看不见。div + JS 手搓的控件如果没写 ARIA 属性,精简快照里可能整个消失。补救是 verbose: true,但那会显著增大输出。
  • 强依赖 Puppeteer 内部 API。 大量 @ts-expect-error + page._client()_targetId_tabIdrequest.id(散见 src/McpPage.ts:233src/TextSnapshot.ts:261src/collectors/PageCollector.ts:189-192src/tools/pages.ts:410)。Puppeteer 内部重构会直接打破这些点。
  • DevTools submodule 跟的是 main 分支。 .gitmodulesbranch = main,上游是活跃开发中的代码,接口变动需要跟进。
  • 对话框会放大所有超时。 页面弹了 alert 而没人处理,渲染进程暂停,后续几乎所有基于 evaluate 的能力都会走到超时分支。项目做了很多防护(blockedByDialog#dialogDetected 短路),但体验依然是"变慢"。
  • beforeunload 的默认策略是接受。 navigate_page 默认 accept(src/tools/pages.ts:207),意味着未保存的表单内容会被丢弃且不询问。
  • 默认持久化 profile。 不加 --isolated 时用 ~/.cache/chrome-devtools-mcp/chrome-profile,登录态会跨会话保留——方便,但也意味着 agent 可能带着你的真实登录态在跑。
  • 暴露面很大,项目自己也这么说。 启动时打的免责声明:"exposes content of the browser instance to the MCP clients allowing them to inspect, debug, and modify any data in the browser or DevTools"(src/index.ts:237-239)。
  • 默认开启使用统计。 可用 --no-usage-statisticsCHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS / CI 环境变量关闭。

8.3 横向对比(同货架 browser-agents)

四种"让 AI 用浏览器"的路线

项目它到底给 agent 什么页面表示浏览器在哪
chrome-devtools-mcp一台 Chrome + 整个 DevTools 的分析能力无障碍树 + uid本地启动或接管
playwright-mcp一台 Playwright 浏览器的操作面无障碍树快照 + ref 定位Playwright 管理
page-agent一段 <script>,agent 活在页面内部页面内构造的文本化 DOM就是用户当前这个网页
steel-browser一个浏览器基础设施 API(会话/代理/指纹)由调用方自己决定自托管服务端
midscene视觉驱动的动作循环(此项属 computer-use)纯截图 + 坐标浏览器或整台桌面

和最接近的 playwright-mcp 比

两者在页面表示上是同一条路线——都用无障碍树而非截图,都给元素发一个短引用让模型指认。差异在别处:

维度chrome-devtools-mcpplaywright-mcp
主打能力调试与分析(trace/insight/heap/issue)自动化操作
底层puppeteer-core + CDPPlaywright
跨浏览器只保证 ChromePlaywright 的多引擎能力
工具实现位置全在本仓库已搬进 Playwright monorepo,本仓库是发行壳
附加形态还提供 chrome-devtools CLI + daemon

一句话选型: 要"让 AI 跑通一个流程",两者都行;要"让 AI 说清楚这个页面为什么慢、哪里泄漏内存、控制台那条报错的原始堆栈在哪",目前只有 chrome-devtools-mcp 把 DevTools 那套整包搬了过来。

一条共同的暗线

这几个项目最后都撞上同一个问题:模型手里的"页面旧状态"和真实页面几乎从不一字不差。 各家的应对不同——

  • chrome-devtools-mcp:用 loaderId + backendNodeId 让 uid 跨快照复用,失效时给三种不同话术的错误;
  • 纯视觉路线(midscene):每一步都重新截图重规划;
  • 页面内路线(page-agent):直接在页面里维护索引,不存在跨进程同步问题。

8.4 想动手改的话

加一个工具,要碰的地方

① src/tools/<模块>.ts 用 defineTool/definePageTool 写定义
② src/tools/tools.ts 如果是新模块,加到 createTools 的展开列表里
③ npm run gen 一条命令跑完:
build → docs:generate → cli:generate
→ update-metrics → format

别手改 src/bin/chrome-devtools-cli-options.tsdocs/tool-reference.md,它们是生成物。

schema 的两条隐含约束

  1. 参数类型必须能被遥测处理。 只支持 ZodString/Number/Boolean/Array/Enum(含 optional/default/nullable/effects 包装),否则 getZodType 抛错(src/telemetry/transformation.ts:39)。
  2. 参数类型太复杂 CLI 生成会失败。 见 7.1。

仓库里还有自定义 ESLint 规则(scripts/eslint_rules),src/tools/input.ts:397 出现过 // eslint-disable-next-line @local/enforce-zod-schema——说明 schema 写法本身也被规则约束着。

测试怎么组织

tests/ 下按源码结构镜像,另有:

目录/文件干什么
tests/e2e/端到端
tests/*.test.js.snapshot快照测试(npm run test:update-snapshots 更新)
tests/roots.test.tsroots 沙箱行为
tests/shutdown.test.ts退出信号处理
tests/network_blocking.test.ts黑白名单
scripts/profile/内存回归(npm run test:memory,阈值 1%)

有些代码专门为可测性开了口子,例如 McpPage.createWaitForHelper 标了 "Public for testability"(src/McpPage.ts:408)、McpContext.from 允许注入 locatorClass(src/McpContext.ts:203-204)。


8.5 总代码地图

按"我想改什么"索引

我想…去看关键符号
加/改一个工具src/tools/*.tsdefineTooldefinePageTool
改工具的开关规则src/tools/categories.tssrc/ToolHandler.tsOFF_BY_DEFAULT_CATEGORIESgetToolStatusInfo
改一次调用的生命周期src/ToolHandler.tsToolHandler.handle
改浏览器启动方式src/browser.tslaunchensureBrowserConnected
改页面/上下文状态src/McpContext.tssrc/McpPage.tsMcpContextMcpPage
改快照格式或 uid 策略src/TextSnapshot.tssrc/formatters/SnapshotFormatter.tsassignIds#formatNode
改输出格式或加一段输出src/McpResponse.tsformat#dataWithPagination
改等待策略src/utils/WaitForHelper.tswaitForEventsAfterAction
改数据保留策略src/collectors/PageCollector.tsmaxNavigationSavedsplitAfterNavigation
改性能/内存分析src/processors/*.tssrc/devtools/DevtoolsUtils.tsgetTraceSummaryHeapSnapshotManager
改 CLI 或 daemonsrc/bin/chrome-devtools.tssrc/daemon/*commandshandleRequest
改遥测src/telemetry/*sanitizeParamsClearcutLogger
加第三方依赖src/third_party/index.ts(所有外部引入的唯一出口)

按文件规模索引(读源码的优先级)

行数文件建议
1493src/McpResponse.ts只读 handle + format 的骨架,if 块按需查
1326src/bin/chrome-devtools-cli-options.ts生成物,当查表用
907src/McpContext.ts前 300 行是核心,后面大半是堆快照转发
882src/McpPage.ts读构造、initemulategetElementByUid
542src/tools/input.ts工具写法的最佳样板
509src/tools/ToolDefinition.ts类型定义为主
394src/ToolHandler.ts全项目最该完整读的一个文件
304src/utils/WaitForHelper.ts第二该完整读的
333src/collectors/PageCollector.ts第三

文档索引

文件内容是否生成物
README.md安装、客户端配置、全部 CLI 参数
docs/tool-reference.md56 个工具的完整参数表
docs/slim-tool-reference.mdslim 模式三个工具
docs/cli.mdCLI 用法
docs/design-principles.md七条设计准则
docs/troubleshooting.md常见问题
CHANGELOG.md变更历史(约 11 万字符)是(release-please)