Chrome DevTools 面向 Agent:Agent Plugins 1.0,以及那个能关掉 JavaScript 的参数
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 的二进制转储基本无输出,现在至少能给出可执行的结论。