跳到主要内容

数据截至 (上游 commit 7e0457a7cbf8)

工具面:69 个工具、快照-ref 定位模型与响应格式

这章讲什么: 模型透过这个 server 能看到什么、能做什么、每次调用回来什么。先讲定位模型(这是整个设计的地基),再给 69 个工具的完整分组表,最后拆响应的分节格式。

前置提醒: 这些工具的实现不在本仓库(见 src/README.md:1-3)。本章的所有事实来自两处可核对的地方:README 里由脚本从编译产物生成的工具目录(README.md:862-1604),以及跑真 server 的集成测试(tests/)。


1. 定位模型:模型怎么「指」一个元素

1.1 它要解决的小问题

模型说「点那个提交按钮」,程序得知道这是页面上哪个 DOM 节点。这个「从自然语言到真实节点」的落地,是所有 browser agent 最容易翻车的一步。

1.2 思路:先编号,再复述

不让模型描述元素,而是先给每个元素发一个号,再让模型复述号码。

① server 抓无障碍树 → 序列化成文本,逐个节点发号
- button "Submit" [ref=e2]


② 整段文本进模型上下文,模型读到编号 e2


③ 模型调 browser_click { element: "Submit button", target: "e2" }


④ server 把 e2 解析回真实节点并执行
回执: page.getByRole('button', { name: 'Submit' }).click()

第 ①③④ 步的字面证据都在同一个测试里(tests/click.spec.ts:31-48)。

1.3 快照长什么样

从两个测试的断言可以还原出格式:

断言片段出处说明
generic [active] [ref=e1]: Hello, world!tests/core.spec.ts:25节点 = 角色 + 状态标记 + ref + 文本内容
- button "Submit" [ref=e2]tests/click.spec.ts:36列表项风格,角色后跟带引号的可访问名
button "Submit" [active] [ref=e2]tests/click.spec.ts:47点击后同一节点多出 [active],ref 编号不变

测试侧解析器把这段内容当 YAML 处理(tests/fixtures.ts:276 剥掉 ```yaml 围栏),说明快照在响应里是以 YAML 代码块形式出现的。

最值得注意的一点: 编号在动作前后保持稳定(e2 → e2),状态变化用独立标记表达。这让模型可以在多轮动作里持续引用同一个编号,而不必每轮重新认一遍元素。

1.4 elementtarget:一个参数给人,一个给机器

几乎所有动作类工具都同时收这两个参数。README 生成出的参数描述原文是:

  • element:「用于取得与该元素交互的许可的人类可读描述」(README.md:874)
  • target:「来自页面快照的精确目标元素引用,或一个唯一的元素选择器」(README.md:875)

为什么要冗余? 因为它们服务两个不同的消费者。element 是给授权界面/审计日志看的——MCP client 弹窗问用户「允许 agent 点击 Submit button 吗」时,展示的是这一段;target 是给执行器用的。把「可解释」和「可执行」拆成两个字段,而不是让程序反过来从选择器生成人话。

browser_drag 把这个模式扩展到两端:startElement / startTarget / endElement / endTarget(README.md:901-909)。

注意 target 的描述里还有一条逃生舱:也接受一个唯一的元素选择器。当快照里没有合适的 ref 时(比如动态生成的元素),模型仍能退回到选择器。

1.5 三条不同的「看页面」方式

工具拿到什么什么时候用依据
browser_snapshot整棵(或子树)无障碍树需要全局理解页面结构README.md:1064-1072
browser_find只有命中节点 + 少量上下文 + 从根起的路径只想找一个元素拿它的 refREADME.md:955-961
browser_take_screenshot图片需要视觉确认README.md:1075-1085

browser_snapshot 有四个参数值得单说(README.md:1068-1071):target 只抓某个子树、filename 存文件而不是塞进响应depth 限制树深、boxes 附带每个元素的视口相对包围盒(CSS 像素)。这四个都是上下文预算控制手段。

browser_take_screenshot 的描述里有一句硬约束:「你不能基于截图执行动作,要执行动作请用 browser_snapshot」(README.md:1077)。截图在这个系统里是只读的旁证,不是坐标来源。


2. 能力门控:默认只给 24 个,其余按需开

2.1 默认工具集被测试逐字钉死

不带任何 --caps 时,tools/list 返回的集合正好是 24 个,测试用一个 Set 相等断言把它锁死(tests/capabilities.spec.ts:21-46)。而完整目录有 69 个——64% 的工具默认不可见

2.2 九个 capability 分组

生成器里那张映射表就是分组的权威定义(update-readme.js:25-38),配合 config.d.ts:19-31ToolCapability 联合类型:

capabilityREADME 标题工具数默认开?
core / core-navigation / core-inputCore automation23
core-tabsTab management1
core-installBrowser installation0(见 §2.4)
configConfiguration1--caps=config
networkNetwork4--caps=network
storageStorage17--caps=storage
devtoolsDevTools11--caps=devtools
visionCoordinate-based6--caps=vision
pdfPDF generation1--caps=pdf
testingTest assertions5--caps=testing

(工具数按 README.md:862-1604 生成段落逐组统计;core* 三个 key 映射到同一个标题,故合并计数。)

2.3 命名规则里藏着开关信息

update-readme.js:60-63capabilityTitle 只有三行,但决定了 README 里每个分组标题长什么样:

function capabilityTitle(capability) {
const title = capabilities[capability];
return capability.startsWith('core') ? title : `${title} (opt-in via --caps=${capability})`;
}

规则就是 core 前缀:core 开头的默认开,标题裸写;其余一律追加「(opt-in via --caps=xxx)」。所以「这个工具默认在不在」这件事,不需要额外维护一张表——写进 capability 名字里了。

2.4 「Browser installation」为什么是空的

README 里这一节渲染出来是一个空的 <details>(README.md:1130-1134)。原因在过滤条件(update-readme.js:49):

let filteredTools = tools.browserTools.filter(tool => tool.capability === capability && !tool.skillOnly);

skillOnly 的工具被排除。也就是说 core-install 下确实注册了工具,但它们被标记为只给 skill 用、不进 MCP 工具目录。这个仓库里看不到 skillOnly 的判定逻辑(在上游),但从这个字段能确认:上游的工具注册表同时服务 MCP 和 CLI+SKILLS 两条产品线,靠 flag 区分。这也印证了 README 开篇那段 MCP vs CLI 的对比(README.md:5-11)。

2.5 一个未在选项表里的遗留别名

--caps=vision 之外,还存在一个老写法 --vision,测试标题直接写着「support for legacy --vision option」(tests/capabilities.spec.ts:69-78),断言它同样能挂上三个 browser_mouse_*_xy 工具。这个选项不在 README 生成的选项表里(README.md:409-454)——说明它被刻意从文档中隐藏,只保留兼容。


3. 69 个工具的完整目录

下面按分组列全名,方便 agent 按任务匹配。详细参数请查 README.md:862-1604 对应位置。

3.1 Core automation(23,默认开)

类别工具
导航browser_navigatebrowser_navigate_backbrowser_close
感知browser_snapshotbrowser_findbrowser_take_screenshotbrowser_console_messagesbrowser_network_requestsbrowser_network_request
输入browser_clickbrowser_typebrowser_press_keybrowser_hoverbrowser_select_optionbrowser_fill_formbrowser_file_uploadbrowser_dragbrowser_drop
其他browser_resizebrowser_wait_forbrowser_handle_dialogbrowser_evaluatebrowser_run_code_unsafe

3.2 其余八组

分组工具
Tab managementbrowser_tabs(一个工具用 action 参数收编 list/new/close/select,README.md:1118-1125)
Configurationbrowser_get_config(返回 CLI + 环境变量 + 配置文件合并后的最终配置,README.md:1140-1143)
Networkbrowser_routebrowser_route_listbrowser_unroutebrowser_network_state_set
Storagebrowser_cookie_{clear,delete,get,list,set}browser_localstorage_{clear,delete,get,list,set}browser_sessionstorage_{clear,delete,get,list,set}browser_storage_statebrowser_set_storage_state
DevToolsbrowser_annotatebrowser_highlightbrowser_hide_highlightbrowser_resumebrowser_start_tracingbrowser_stop_tracingbrowser_start_videobrowser_stop_videobrowser_video_chapterbrowser_video_show_actionsbrowser_video_hide_actions
Coordinate-basedbrowser_mouse_click_xybrowser_mouse_move_xybrowser_mouse_drag_xybrowser_mouse_downbrowser_mouse_upbrowser_mouse_wheel
PDF generationbrowser_pdf_save
Test assertionsbrowser_generate_locatorbrowser_verify_element_visiblebrowser_verify_text_visiblebrowser_verify_list_visiblebrowser_verify_value

3.3 几个值得单独说的工具

browser_run_code_unsafe —— 描述里自带风险声明:「不安全:在 Playwright server 进程中执行任意 JavaScript,等价于 RCE」(README.md:1044)。它接受一个 async (page) => {...} 形式的函数,或者用 filename 从文件加载;两者都给时 code 被忽略(README.md:1046-1047)。

它和 browser_evaluate 的区别是本质性的:

代码跑在哪能碰到什么
browser_evaluate页面里页面 DOM、window
browser_run_code_unsafePlaywright server 进程里整个 page API,以及该进程能碰到的一切

注意 browser_run_code_unsafe 在默认 24 个工具里(tests/capabilities.spec.ts:41)。这是这个 server 默认权限面里最需要留意的一项。

browser_verify_* 系列 —— 断言类工具的参数描述里居然写了「怎么从快照里找到这个值」。比如 browser_verify_element_visiblerole 参数说明是:「元素的 ROLE。可以在快照里这样找到:- {ROLE} "Accessible Name":」(README.md:1567)。工具描述本身在教模型怎么读快照。

browser_network_requests + browser_network_request 的两级取数 —— 前者返回一个编号列表,后者用编号取单条的完整头和体(README.md:1013-1020README.md:1002-1009)。前者默认还会过滤掉成功的静态资源(static 参数默认 false),并支持正则过滤 URL。

这是 browser_find 之外的第二处两级取数设计:先给便宜的索引,再按需取昂贵的详情。同样的模式还出现在 filename 参数上——browser_snapshotbrowser_evaluatebrowser_console_messagesbrowser_network_request(s) 都允许把结果写文件而不是回响应,把大结果挡在上下文之外。


4. 响应长什么样

4.1 分节 markdown

测试里的响应解析器用 ^### 切分整段文本(tests/fixtures.ts:295-309parseSections),再按小节名取值(tests/fixtures.ts:250-293parseResponse)。从它认识的小节名,能反推出 server 的响应结构:

小节标题内容
### Result工具的主要返回值
### Ran Playwright code等价的 Playwright 代码,包在 ```js 围栏里
### Snapshot无障碍快照,YAML 围栏;一个 [Snapshot](相对路径) 链接
### Open tabs当前标签页列表
### Page state页面状态
### New console messages自上次以来的新 console 输出
### Modal state弹窗/对话框状态
### Downloads下载列表
### Error错误信息

4.2 快照可以落盘,也可以内联

解析器里有一段分支很说明问题(tests/fixtures.ts:267-278):它先尝试用 \[Snapshot\]\(([^)]+)\) 匹配一个 markdown 链接,匹配上就去读那个文件;匹配不上才当作内联的 YAML 块处理。

也就是说,快照太大时 server 会把它写进输出目录、响应里只留一个链接。上下文预算的保护做在了协议层,而不是指望调用方自觉。

4.3 图片走 attachments

tests/fixtures.ts:265 里,attachments 取的是 response.content.slice(1)——第 0 项是那段分节文本,之后的所有 content 项都是附件(截图之类)。配合 imageResponses 配置(allow / omit,config.d.ts:220-223),可以对不支持图片的客户端整体关掉。

4.4 一次调用的响应装配示意

下面是一段示意,非源码,用来说明「一次动作为什么能同时回代码和新快照」:

// 示意,非源码:一个动作类工具的响应装配思路
async function handleAction(tool, params) {
const code = tool.toPlaywrightCode(params); // 先算出等价代码
await tool.execute(params); // 再真的执行
await settle(); // 等触发的导航/请求安定下来
return [
`### Ran Playwright code\n\`\`\`js\n${code}\n\`\`\``,
`### Snapshot\n${await captureAriaSnapshot()}`, // 动作后的新世界
].join('\n\n');
}

重点看 settle() 那一步。 配置里有一个专门的 timeouts.settle,注释写着「每次动作后等待被触发的工作(导航、请求)安定下来再响应,默认 500ms」(config.d.ts:214-217)。这是为什么模型不需要在点击后再手动调一次 browser_wait_for ——等待被内建进了每次动作的尾部。


5. read-only 标记的实际含义

生成器把每个工具的 type === 'readOnly' 渲染成一行(update-readme.js:93)。这个标记给 MCP client 用来决定要不要弹窗问用户。

有几处标注方式值得注意:

工具read-only为什么
browser_take_screenshottrue虽然会写文件,但不改页面状态
browser_navigatefalse导航改变了页面状态
browser_wait_forfalse见下
browser_verify_element_visiblefalse见下
browser_highlight / browser_annotatetrue尽管往页面上画了覆盖层

两处反直觉的标注: browser_wait_for(README.md:1110)和几个 browser_verify_*(README.md:1569 等)都标成非只读,尽管它们直觉上「只是看」。可能的解释是这些工具会推进页面时间/触发重试从而改变可观察状态——但这个仓库里没有判定逻辑,无法确证,只能记录现象。

反过来 browser_highlight 标成只读(README.md:1525),说明「只读」在这里的口径是不改变页面的语义状态,而不是「完全不产生副作用」。


6. 边界:这一章确认不了的事

  • ref=eN 的分配算法、跨导航是否复用、DOM 大改后如何失效 —— 上游实现。
  • --caps 到底在注册阶段还是列举阶段过滤工具 —— 上游实现。
  • browser_find 的上下文行数、路径拼接规则 —— 只有描述,无实现可读。
  • skillOnly 的判定标准 —— 只在 update-readme.js:49 见到字段名。
  • 快照落盘的大小阈值 —— 只在 tests/fixtures.ts:269 见到消费侧。

7. 代码地图

主题文件路径符号名 / 锚点
完整工具目录(生成)README.md<!--- Tools generated by update-readme.js -->End of tools generated section
分组定义与标题规则update-readme.jscapabilitiescapabilityTitle
skillOnly 过滤update-readme.jstoolsByCapability 构建循环
read-only 渲染update-readme.jsformatToolForReadme
capability 类型枚举config.d.tsToolCapability
默认 24 个工具集合tests/capabilities.spec.tstest('test snapshot tool list')
--caps 追加验证tests/capabilities.spec.tstest('test capabilities (pdf)')(vision)
遗留 --vision 别名tests/capabilities.spec.tstest('support for legacy --vision option')
快照 + ref + 代码回执tests/click.spec.tstest('browser_click')
快照最小样例tests/core.spec.tstest('browser_navigate')
响应分节结构tests/fixtures.tsparseResponseparseSections
快照落盘链接解析tests/fixtures.tsparseResponse 中的 \[Snapshot\]\(…\) 分支
动作后等待窗口config.d.tstimeouts.settle
图片响应开关config.d.tsimageResponses
快照模式与包围盒config.d.tssnapshot.modesnapshot.boxes