Chrome DevTools 面向 Agent:Agent Plugins 1.0,以及那个能关掉 JavaScript 的参数

·11 分钟阅读·Evergreen Tools Team

Chrome 从 2026 年下半年开始改成一个月发两次版本,DevTools 的更新日志也随之从「每个版本一篇」变成「每月一篇盘点」。这篇覆盖的是 Chrome 153 和 Chrome 154 这一个月里落到稳定版的东西,发布在 2026 年 9 月 22 日。其中最值得读的是 DevTools for agents 那一段——Chrome DevTools MCP server 一次性拿到了安全控制、性能调优和内存分析三组新能力。换句话说,浏览器调试器正在变成一个 Agent 可以调用的工具,而 Chrome 同时开始给它上锁。

笔记本电脑上的浏览器窗口

浏览器调试器正在变成 Agent 可以调用的工具,同时开始上锁。

先看清楚这次给了什么,又收回了什么

给的部分:Agent Plugins 1.0 包格式支持,agent 可以加载并执行模块化的插件扩展;按需加载 source map,避免大站上 source map 把内存吃满;按 retained size 或属性名查询堆快照,看边的 self size 和 retained size,检查 V8 执行上下文持有的内存;PWA 自动化的专用工具,覆盖安装和生命周期检查;list_console_messages 可以带上可选的堆栈信息。

收的部分更有意思。新增了 JavaScript 执行控制:可以配置 --no-javascript-evaluation,或者直接关掉 JavaScript 执行相关的工具,从而在不执行脚本的前提下检查页面——而且这个限制跨导航和初始化脚本持续有效。这一条是整个更新里我最看重的。

原因很直接:一个能在你已登录的浏览器会话里执行任意 JavaScript 的 agent,就是能读你 session cookie 并把它发出去的 agent。所以除非你的任务就是调试 JS 本身,否则这个开关应该默认关掉。工具能力越强,默认值就越应该保守。

// Wire the Chrome DevTools MCP server into your coding agent. The server exposes
// the browser as tools: navigate, snapshot, console, network, performance, memory.
{
  "mcpServers": {
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest"],

      // Day one changes. The default grants unrestricted filesystem paths and
      // allows JavaScript evaluation. Neither is what you want on a public site.
      "env": {
        "CHROME_DEVTOOLS_MCP_ARGS": [
          "--no-javascript-evaluation",
          "--headless",
          "--isolated"
        ].join(" ")
      }
    }
  }
}
// Confirm the exact flag names against the chrome-devtools-mcp repository before
// you ship this. The CLI gained flags in this release; older docs do not list them.

Agent Plugins 1.0:新能力也是新的攻击面

Agent Plugins 1.0 让 agent 能加载并执行模块化插件。这确实是能力提升,但请把它当成「你现在要审的是插件,不只是 prompt」。插件是代码,代码有权限。

清单文件通常很短,短到你会想跳过去。别跳。清单里唯一需要认真读的是 permissions 那一段,因为那决定了这个插件最坏能做什么。我写清单时的一条原则是:没必要的权限一个都不给。一个只负责录制性能 trace 的插件不需要 browser.evaluate。

还有一个容易被忽略的点:插件是「可被 agent 加载并执行」的。也就是说,如果你从某个社区仓库直接拉一个插件进来,你等于把那段代码加进了一个有浏览器访问权的执行环境。这跟 npm 依赖的风险模型是一样的,只是大家还没习惯在这个位置警惕。

# Agent Plugins 1.0: agents can load and execute modular plugin extensions. That is
# capability and that is also the new attack surface. An Agent Plugins 1.0 manifest
# is small, so read every field of every plugin you install.
# plugin.yaml — the shape, not a vendor schema
name: chrome-perf-audit
version: 0.1.0
description: Record a performance trace and summarise long tasks for an agent.
entry: ./dist/index.js
permissions:
  - browser.navigate
  - browser.performance.trace
  - filesystem.read:workspace
# Deliberately absent: browser.evaluate. If a plugin does not need JavaScript
# evaluation, do not grant it. Capability you did not grant is capability that
# cannot be abused.
inputs:
  - name: url
    type: string
    required: true
outputs:
  - name: trace_summary
    type: json

堆快照查询:把问题问对

以前让 agent 查内存泄漏,它会让你导一个 900 MB 的堆快照,然后你也不知道该看哪一行。现在多了 query_heapsnapshot:可以按 retained size 或属性名查对象,可以看边的 self size 和 retained size,可以检查 V8 执行上下文持有的内存。

工具变了,提问方式也得变。「找出内存泄漏」不是一个查询,「什么东西 retained 超过 5 MB」才是。我一般的顺序是:先按 retained size 排序,看最大的持有者;再按属性名查,当你已经知道泄漏对象持有什么字段的时候。

这个改变看似小,实际影响很大。以前 agent 在内存问题上的产出基本不可用,因为它只擅长读文本,不擅长读一个几百 MB 的二进制转储。现在它至少能问出一个有答案的问题了。

# Heap snapshot inspection is now queryable. Instead of dumping a 900 MB snapshot
# at the agent and hoping, ask a specific question.
# Tool surface: query_heapsnapshot — query objects by retained size or property name,
# view self and retained sizes for edges, inspect memory retained by V8 contexts.

def ask_agent_to_find_the_leak(agent):
    # 1. Take the snapshot after the workflow that you suspect leaks.
    agent.call("performance_take_memory_snapshot", {"reportProgress": True})

    # 2. Ask by retained size first: the biggest retainer is usually the culprit.
    top = agent.call("query_heapsnapshot", {
        "query": "retained_size > 5000000",
        "limit": 20,
    })
    for node in top["nodes"]:
        print(f'{node["retainedSize"] / 1e6:8.1f} MB  {node["name"]}  {node["type"]}')

    # 3. Then by property name, when you know what the leaked object holds.
    leaked = agent.call("query_heapsnapshot", {
        "propertyName": "detachedNode",
        "limit": 50,
    })
    return {"largest": top["nodes"][:5], "byProperty": leaked["count"]}

# The point is the shape of the question. "Find the memory leak" is not a query.
# "What retains more than 5 MB" is.
深色屏幕上的终端输出

「找出内存泄漏」不是查询,「retained 超过 5 MB」才是。

文件系统根目录:那个默认值必须改

这一条很容易看漏:CLI 现在默认带 --allow-unrestricted-paths,同时支持可配置的文件系统根目录。前半句是默认放开,后半句是你可以收窄。也就是说,开箱状态下它是宽的。

在 CI 里跑 agent 的时候,这个默认值尤其危险。CI 机器上往往挂着部署密钥、制品凭证、云厂商凭据。一个可以把输出写到任意路径的 agent,配合一个被污染的网页,就是一个数据外发通道。

所以我的构建脚本里做了一件事:显式传 --filesystem-root,并且不依赖任何默认值。代码里那个 build_args 就是最小实现,allow_js 和 roots 两个参数决定了这次会话是「只读检查」还是「可以动手」。把它当策略文件看,别当偏好设置看。

# Security posture for an agent that drives a real browser. These are the controls,
# not the defaults you should trust.
import subprocess, shlex

BASE = "npx -y chrome-devtools-mcp@latest"

def build_args(allow_js: bool, roots: list[str], on_demand_maps: bool = True) -> str:
    args = [BASE]

    # Inspect without evaluating. Applies across navigations and init scripts,
    # so a page cannot re-enable evaluation by navigating.
    if not allow_js:
        args.append("--no-javascript-evaluation")

    # Filesystem roots are configurable. --allow-unrestricted-paths is on by
    # default, which is the opposite of what you want in CI.
    for r in roots:
        args.append(f"--filesystem-root={r}")

    if on_demand_maps:
        args.append("--source-maps=on-demand")

    return " ".join(shlex.quote(a) for a in args)

print(build_args(allow_js=False, roots=["/workspace/reports"]))
print(build_args(allow_js=True, roots=[]))

# Read this as a policy, not a preference. An agent that can evaluate arbitrary
# JavaScript in a logged-in browser session is an agent that can read your session
# cookie and post it somewhere. Turn it off unless the task is literally JS debugging.

还有两个值得自动化的东西

第一个是 CPU 性能档位覆盖。现在可以通过 CDP 配置、计算并下发校准过的 CPU 性能档位覆盖(Emulation.setCPUPerformanceOverride),这意味着 CI 终于可以复现「你的站在中端手机上很慢」这件事,而不是每次开会凭感觉争。把档位钉死,trace 就能周与周之间比较。

第二个是广告脚本追踪。Application 面板新增了 Ads 子面板,集中调试和优化整页广告相关信息:四个实验性 CrUX 指标的看板(视口广告密度、视口广告数量、广告总 CPU 占用、广告总网络占用)、按广告元素拆分 CPU 与网络、主框架里的广告脚本表,以及一个高亮页面上广告的开关。

如果你在跑广告,这四个数字就是该盯的,因为将来 Core Web Vitals 的惩罚大概率就是从这里算出来的。顺便说一句,这个更新里还有不少顺手的小改进——Elements 面板可以继续显示曾经匹配但已失效的样式规则、adorners 可以用键盘聚焦、拖拽排序跨 shadow DOM 可靠了。这些不性感,但每天都会用到。

# Two more things worth automating: reproducible perf tiers and ad-cost measurement.
# CPU performance tier overrides are reachable over CDP now, which means CI can
# reproduce "your site is slow on a mid-range phone" instead of arguing about it.
import json, websocket

def override_cpu_tier(ws_url: str, tier: int) -> None:
    ws = websocket.create_connection(ws_url)
    ws.send(json.dumps({
        "id": 1,
        "method": "Emulation.setCPUPerformanceOverride",
        "params": {"tier": tier, "enabled": True},
    }))
    print(ws.recv())
    ws.close()

# tier values map to the calibrated performance tiers DevTools shows in the
# Performance panel. Pin one in CI so a trace is comparable week to week.
override_cpu_tier("ws://127.0.0.1:9222/devtools/page/ABC", tier=3)

# The Application panel now tracks ad scripts with four experimental CrUX metrics:
# viewport ad density, viewport ad count, total CPU usage by ads, total network
# usage by ads. If you run ads, those four numbers are the ones to watch, because
# they are the ones a Core Web Vitals penalty will eventually be computed from.
屏幕上密集的代码

一个能执行任意 JS 的 agent,也能读走你的 session cookie。

📌 常见问题 FAQ

Chrome DevTools MCP server 是什么?

它是 Chrome 官方的 MCP 服务器(仓库 ChromeDevTools/chrome-devtools-mcp),让编程 agent(例如 Antigravity、Claude、Cursor、Copilot)控制和检查一个运行中的 Chrome 浏览器。它把浏览器能力包装成工具:导航、快照、console、网络、性能和内存。

--no-javascript-evaluation 到底挡住什么?

它让你在不执行脚本的前提下检查页面。官方说明是这个限制跨导航和初始化脚本持续生效,也就是说页面不能靠重新导航来重新打开 JS 执行。对于检查第三方页面或者不可信内容,这是应该默认打开的模式。

为什么说 --allow-unrestricted-paths 是个危险的默认值?

因为它在开箱状态下允许 agent 访问不受限的文件系统路径。CI 环境里常常挂着部署密钥和云凭据,一个能往任意路径写文件的 agent 意味着存在数据外发通道。CLI 同时支持可配置的文件系统根目录,应该显式设置而不是依赖默认。

Agent Plugins 1.0 需要审什么?

审 permissions。插件是可执行代码,权限清单决定了它最坏能做什么。没必要的权限不要授予,比如一个只录制性能 trace 的插件不需要 JavaScript 执行权限。从社区拉插件时,用对待 npm 依赖的警惕程度对待它。

query_heapsnapshot 能替代完整的堆快照分析吗?

不能完全替代,但它把最常见的两个问题变成了可查询的:谁 retained 得最多,以及某个属性名下的对象有多少。以前 agent 面对几百 MB 的二进制转储基本无输出,现在至少能给出可执行的结论。