Essay 009 · Agent
Chrome DevTools MCP 为什么会开新窗口:Tools、参数与窗口复用边界
基于 @async23/chrome-devtools-mcp 1.7.0 的本机硬约束构建,说明 new_page 如何固定使用默认 BrowserContext 后台创建页面,以及这一做法仍受现有 Window 状态限制的边界。
首版:梳理 Tools、BrowserContext、Window 与 new_page 硬约束
Version 1.0一句话结论:本机硬约束版本的
new_page只接收url和可选timeout,MCP 内部固定使用background: true与默认 BrowserContext;已有可用 Window 时会创建后台 Tab,但当前 API 仍不能在所有浏览器状态下硬性禁止新 Window。
适用版本:基于
@async23/chrome-devtools-mcp 1.7.0的本机硬约束构建673fcb1、Puppeteer25.5.0、Chrome151.0.7922.138。tool schema 按--browserUrl=http://127.0.0.1:9222和默认类别配置重新获取,共 29 个 Tools。
1. MCP 暴露的全部 Tools
以下列表来自本机新启动的
@async23/chrome-devtools-mcp 1.7.0 硬约束构建
673fcb1 实际暴露的默认 tool schema,共 29 个 Tools。
| 序号 | 类别 | Tool | 参数 |
|---|---|---|---|
| 1 | Navigation | close_page |
pageId: number |
| 2 | Navigation | list_pages |
无 |
| 3 | Navigation | navigate_page |
type?: url/back/forward/reload、url?: string、ignoreCache?: boolean、handleBeforeUnload?: accept/dismiss、initScript?: string、timeout?: number |
| 4 | Navigation | new_page |
url: string、timeout?: number |
| 5 | Navigation | select_page |
pageId: number、bringToFront?: boolean |
| 6 | Navigation | wait_for |
text: string[]、timeout?: number |
| 7 | Emulation | resize_page |
width: number、height: number |
| 8 | Input | click |
uid: string、dblClick?: boolean、includeSnapshot?: boolean |
| 9 | Input | drag |
from_uid: string、to_uid: string、includeSnapshot?: boolean |
| 10 | Input | fill |
uid: string、value: string、includeSnapshot?: boolean |
| 11 | Input | fill_form |
elements: {uid,value}[]、includeSnapshot?: boolean |
| 12 | Input | hover |
uid: string、includeSnapshot?: boolean |
| 13 | Input | press_key |
key: string、includeSnapshot?: boolean |
| 14 | Input | type_text |
text: string、submitKey?: string |
| 15 | Input | upload_file |
filePath: string、uid: string、includeSnapshot?: boolean |
| 16 | Input | handle_dialog |
action: accept/dismiss、promptText?: string |
| 17 | Debugging | take_snapshot |
filePath?: string、verbose?: boolean |
| 18 | Debugging | take_screenshot |
filePath?: string、format?: png/jpeg/webp、fullPage?: boolean、quality?: number、uid?: string |
| 19 | Debugging | evaluate_script |
function: string、args?: string[]、dialogAction?: string、filePath?: string |
| 20 | Console | get_console_message |
msgid: number |
| 21 | Console | list_console_messages |
includePreservedMessages?: boolean、pageIdx?: number、pageSize?: number、serviceWorkerId?: string、types?: enum[] |
| 22 | Network | get_network_request |
reqid?: number、requestFilePath?: string、responseFilePath?: string |
| 23 | Network | list_network_requests |
includePreservedRequests?: boolean、pageIdx?: number、pageSize?: number、resourceTypes?: enum[] |
| 24 | Emulation | emulate |
colorScheme?: dark/light/auto、cpuThrottlingRate?: number、extraHttpHeaders?: string、geolocation?: string、networkConditions?: enum、userAgent?: string、viewport?: string |
| 25 | Audit | lighthouse_audit |
device?: desktop/mobile、mode?: navigation/snapshot、outputDirPath?: string |
| 26 | Performance | performance_analyze_insight |
insightName: string、insightSetId: string |
| 27 | Performance | performance_start_trace |
autoStop?: boolean、filePath?: string、reload?: boolean |
| 28 | Performance | performance_stop_trace |
filePath?: string |
| 29 | Memory | take_heapsnapshot |
filePath: string |
2. 可能引发新窗口的 Tools
29 个 Tools 中,以下 5 个可能直接或间接导致桌面 Chrome 出现新窗口:
重点 Tools
| 序号 | Tool | 参数 | 开窗路径 | 是否依赖页面行为 |
|---|---|---|---|---|
| 1 | new_page |
url: string、timeout?: number |
MCP 固定在默认 BrowserContext 后台创建 page/target,Chrome 再决定放入已有 Window 还是新 Window | 否;取决于普通 Profile 和现有 Window 状态 |
| 2 | click |
uid: string、dblClick?: boolean、includeSnapshot?: boolean |
点击 target="_blank" 链接或触发
window.open() |
是 |
| 3 | press_key |
key: string、includeSnapshot?: boolean |
激活链接、提交表单或触发页面键盘事件 | 是 |
| 4 | type_text |
text: string、submitKey?: string |
submitKey
可能提交表单或触发页面脚本;单纯输入文字不会开窗 |
是 |
| 5 | evaluate_script |
function: string、args?: string[]、dialogAction?: string、filePath?: string |
执行 window.open(),或调用页面中带有开窗行为的代码 |
部分依赖 |
重点 Tools 的参数
new_page
url:新 Page 要打开的 URL。timeout:等待 Page 打开的最长毫秒数;0使用默认超时。
本机硬约束构建不暴露 background 和
isolatedContext;传入这两个字段会按未知参数拒绝。
click
uid:要点击的元素 ID,取自最新的页面快照。dblClick:是否双击;默认false。includeSnapshot:操作后是否顺便返回页面的文字与控件快照;true近似于点击后再调用take_snapshot,默认false,与开窗无关。
press_key
key:要按下的按键或组合键,如Enter、Control+A。includeSnapshot:操作后是否顺便返回页面的文字与控件快照;true近似于按键后再调用take_snapshot,默认false,与开窗无关。
type_text
text:输入到当前已聚焦控件中的文字。submitKey:输入后追加按下的键,如Enter、Tab、Escape。
evaluate_script
function:要在当前页面执行的 JavaScript 函数。args:传给函数的元素uid列表,按顺序对应函数参数。dialogAction:脚本触发弹窗时选择accept、dismiss,或提供 prompt 文本。filePath:把执行结果写入指定文件,而不是直接返回。
3. 开窗行为的可控程度
“可控”是指:无需穷举任意网页的 HTML 和 JavaScript,仅根据 tool 参数、MCP 源码和浏览器状态,就能判断是否存在开窗行为。
| 序号 | Tool | 可控程度 | 原因 | 结论范围 |
|---|---|---|---|---|
| 1 | new_page |
高 | 创建 page 是 tool 的直接职责,窗口选择规则可沿 MCP、Puppeteer 和 Chrome 源码确定 | 作为主要研究对象 |
| 2 | evaluate_script |
中 | 执行的函数由我们提供,但函数仍可调用 window.open()
或页面自有代码 |
约束脚本内容,不尝试穷举页面代码 |
| 3 | click |
低 | 结果取决于目标元素、target 属性和点击事件处理器 |
只记录风险,不穷举页面行为 |
| 4 | press_key |
低 | 结果取决于当前焦点元素和页面键盘事件处理器 | 只记录风险,不穷举页面行为 |
| 5 | type_text |
低 | 结果取决于 submitKey、表单目标和页面提交逻辑 |
只记录风险,不穷举页面行为 |
new_page 是“窗口复用”问题的主要研究对象。其余 4 个 Tools
都可能间接触发窗口,但无法脱离具体页面给出穷尽式结论。
4. 术语与对象关系
核心术语
| 序号 | 术语 | 所属范围 | 定义 |
|---|---|---|---|
| 1 | user-data-dir |
磁盘 | Chrome 的数据根目录,包含 Local State 以及
Default、Profile 1 等 Profile
数据目录;目录本身不是运行实例 |
| 2 | Chrome 实例 | 运行时 | 一套正在运行的 Chrome,由一个 Browser
主进程及其子进程组成,并使用一个
user-data-dir;一个实例可以加载该目录中的多个普通
Profile |
| 3 | Profile | Chrome 实现 | Chrome 的身份与存储对象。普通 Profile 通常持久化在
user-data-dir 的子目录中;off-the-record Profile
是运行时临时对象 |
| 4 | BrowserContext | Chrome/CDP/Puppeteer | 身份与存储隔离范围,决定 cookies、storage 等数据是否共享;Chrome 当前使用 Profile 对象实现这些范围 |
| 5 | 默认 BrowserContext | Chrome/CDP/Puppeteer | Chrome/Puppeteer 已有、不是通过 CDP
Target.createBrowserContext 创建的
Context;在本文调用链中,未传 browserContextId 时使用
Chrome 选中的普通 Profile |
| 6 | 非默认 BrowserContext | Chrome/CDP/Puppeteer | 通过 CDP Target.createBrowserContext 创建并取得
browserContextId 的 Context;Chrome 当前为其创建独立的
off-the-record Profile |
| 7 | Window | Chrome 界面 | macOS 桌面上的 Chrome 窗口;一个 Window 关联一个 Profile,不能混放属于不同 Profile 的 Tab |
| 8 | Tab | Chrome 界面 | Window 标签栏中的标签页;new_page
创建的页面目标最终成为已有 Window 中的新 Tab,或新 Window 中的第一个
Tab |
“默认 BrowserContext”和“非默认 BrowserContext”是 Chrome/CDP/Puppeteer
概念。“隔离 BrowserContext”描述非默认 BrowserContext
的隔离性质;isolatedContext 则是基础版本曾使用的 MCP
参数名。
Default 是本机 Profile 数据目录的名称;“默认
BrowserContext”是运行时/API
概念。二者在本机当前调用链中相互对应,但不是同一个术语。
new_page
涉及的对象关系
Chrome 实例(使用一个 user-data-dir)
├── 默认 BrowserContext
│ └── Chrome 选中的普通 Profile
│ └── 一个或多个 Window
│ └── 一个或多个 Tab
└── 一个或多个非默认 BrowserContext
└── 各自的 off-the-record Profile
└── 零个或多个 Window
└── 零个或多个 Tab
源码与 API 术语
| 序号 | 术语 | 准确定义 | 与本文的关系 |
|---|---|---|---|
| 1 | MCP Server | 接收 tool 调用并通过 Puppeteer/CDP 控制 Chrome 的 Node.js 进程 | 与 Chrome 实例是两个独立进程体系 |
| 2 | CDP | Chrome DevTools Protocol;提供
Target.createBrowserContext、Target.createTarget
等协议方法 |
MCP 经 Puppeteer 调用 Chrome 的底层协议 |
| 3 | browserContextId |
CDP 为非默认 BrowserContext 返回的匿名 ID;创建 Target 时可用它指定目标 Context | Chrome 识别该 ID,不识别 MCP 自定义名称 |
| 4 | isolatedContext |
基础版本曾由 new_page 暴露,用于把名称映射到非默认
BrowserContext;本机硬约束构建已从公开 schema 删除该参数 |
不是 Chrome/CDP 参数;McpContext
仍保留内部映射能力,但公开 new_page 无法使用它创建页面 |
| 5 | off-the-record Profile | 不按普通 Profile 方式持久化浏览数据的临时 Profile | Chrome 当前用它实现 CDP 创建的非默认 BrowserContext |
| 6 | Page | Puppeteer 对页面 Target 的操作对象,表示页面内容与操作接口 | 在 new_page 调用链中对应新 Tab 内的网页,但 Page
不等同于 Tab 这个界面容器 |
| 7 | Target | CDP 的可调试实体,可表示 page、service worker 等多种对象 | new_page 创建的是页面 Target,不是所有 Target 都是
Tab |
| 8 | Browser 主进程 | 管理 Profile、Window、Tab、CDP 会话及 Chrome 子进程的操作系统进程 | 是 Chrome 实例的核心进程 |
| 9 | Renderer | Chrome 用于渲染页面及 frame 的子进程;受 Site Isolation 等规则影响,与 Tab 不保证一一对应 | 不参与 Chrome 对新 Tab 应进入已有 Window 还是新 Window 的选择 |
| 10 | 前台 / 后台 | 新 Tab 是否成为活动 Tab,以及对应 Window 是否主动获得焦点的状态 | 不表示 Window 是否存在,也不等于 headless |
| 11 | headless | Chrome 不显示常规桌面浏览器界面的运行模式 | 与 background: true 是不同概念 |
| 12 | newWindow |
CDP Target.createTarget 的可选参数;当前 Chrome
中,true 要求新建 Window,显式 false
要求使用已有兼容 Window,省略时允许在没有兼容 Window 时回退为新建
Window |
当前 MCP new_page 未暴露该参数 |
| 13 | windowId |
CDP Browser.WindowID,用于查询或修改已有 Window
的状态和尺寸 |
Target.createTarget 不接受
windowId,因此不能用它指定新 Tab 落入哪个现有 Window |
5. 本机 Chrome 环境
以下内容是 2026-08-16 22:39:24 +08:00
的检查快照。进程状态和非默认 BrowserContext 数量会随 Chrome、MCP
的运行情况变化。
磁盘:user-data-dir
与普通 Profile
在 ~/Library/Application Support/Google 范围内,检测到 5
个包含 Local State 和 Profile 元数据的目录:
| 序号 | 目录 | Local State 记录的普通 Profile 目录 |
检查时的运行状态 | 说明 |
|---|---|---|---|---|
| 1 | ~/Library/Application Support/Google/Chrome |
Default |
未运行 | Chrome 默认 user-data-dir |
| 2 | ~/Library/Application Support/Google/Chrome-CDP |
Default |
正在运行 | 该目录对应的 Chrome 实例监听 127.0.0.1:9222 |
| 3 | ~/Library/Application Support/Google/Chrome-backup |
Default |
未运行 | 结构符合 user-data-dir;可能只是备份,未确认曾用于启动
Chrome |
| 4 | 自定义 user-data-dir A |
Default |
未运行 | 独立数据目录;具体名称不公开 |
| 5 | 自定义 user-data-dir B |
Default |
未运行 | 独立数据目录;具体名称不公开 |
这里能确定的是 5 个目录各保存了一份名为 Default 的普通
Profile 数据。由于 Chrome-backup
可能是复制出来的备份,不能仅凭目录结构断言本机实际使用过 5
个彼此独立的普通 Profile。
运行时:Chrome 实例与 BrowserContext
| 序号 | 检查项 | 数量或结果 | 说明 |
|---|---|---|---|
| 1 | 正在运行的 Chrome 实例 | 1 | 使用 Chrome-CDP,监听 9222 |
| 2 | 该实例当前使用的普通 Profile 数据目录 | Chrome-CDP/Default |
该 user-data-dir 只记录了一个普通 Profile |
| 3 | 默认 BrowserContext | 1 | 隐式存在,不包含在 Target.getBrowserContexts
的返回列表中 |
| 4 | CDP 返回的非默认 BrowserContext | 6 | 由 Target.getBrowserContexts 实测;Chrome 当前以临时
off-the-record Profile 实现 |
非默认 BrowserContext 没有可供 --profile-directory
选择的普通 Profile 目录,也不应计入磁盘上的普通 Profile 数据数量。
6. new_page
的本机硬约束
对外 schema
Agent 只需要调用:
{
"url": "https://example.com"
}timeout 可选:
| 序号 | 参数 | 类型 / 必填 | 作用 |
|---|---|---|---|
| 1 | url |
string / 是 |
新 Page 要加载的 URL |
| 2 | timeout |
number / 否 |
等待 Page 打开的最长毫秒数;0 使用默认超时 |
background 和 isolatedContext 不在 schema
中。调用方传入其中任一字段时,ToolHandler
会按未知参数拒绝请求,不会创建 Page。
MCP 内部固定行为
src/tools/pages.ts 的 handler 固定调用:
context.newPage(true, undefined);| 序号 | 内部参数 | 固定值 | 结果 |
|---|---|---|---|
| 1 | background |
true |
新 Page 不主动成为前台 Tab,也不主动前置 Window |
| 2 | isolatedContextName |
undefined |
使用默认 BrowserContext,不通过 new_page 创建非默认
BrowserContext |
前后台与 Context 的选择已经从 Agent 输入移到 MCP 执行层,不再依赖提示词约束。
Window 复用条件
默认 BrowserContext 对应的普通 Profile 已有可用 Window 时,新 Page 会成为该 Window 中的后台 Tab。检查快照中已有的 6 个非默认 BrowserContext 不参与这次调用。
如果默认 BrowserContext 没有可用 Window,Chrome 仍可能创建一个不主动获得焦点的新 Window。硬约束固定的是 MCP 参数,不改变 Chrome 在无可复用 Window 时的回退行为。
运行时验证
2026-08-16 22:42:27 +08:00 通过本机 current
入口执行端到端验证:
| 序号 | 检查项 | 结果 |
|---|---|---|
| 1 | 默认暴露的 Tool 数量 | 29 |
| 2 | new_page 对外参数 |
仅 url、timeout |
| 3 | url 必填状态 |
必填 |
| 4 | 旧 background 参数 |
按未知参数拒绝,未创建 Page |
| 5 | 旧 isolatedContext 参数 |
按未知参数拒绝,未创建 Page 或 BrowserContext |
| 6 | 合法 new_page 调用 |
在默认 BrowserContext 创建 Page |
| 7 | Window 数量 | 调用前后均为 1,复用现有 Window |
| 8 | 非默认 BrowserContext 数量 | 调用前后均为 6,没有创建新 Context |
| 9 | 验证 Page | 验证后已关闭 |
API 能力边界
new_page 没有暴露 CDP 的 newWindow
参数。CDP Target.createTarget 本身也不接受
windowId,所以当前调用链无法指定新 Tab 必须进入某个现有
Window。
必须绝对禁止新 Window 时,不调用 new_page,改用
list_pages →
select_page({bringToFront: false}) →
navigate_page 复用现有 Tab。
7. 参考资料
- Async23
Chrome DevTools MCP 本机硬约束源码(commit
673fcb1) - ChromeDevTools Chrome DevTools MCP 官方仓库
- Chrome DevTools MCP 本机硬约束 Tool Reference
- 本机硬约束版本的
new_pageTool 定义 - MCP
BrowserContext 映射与
newPage实现 - Puppeteer 25.5.0 BrowserContext API
- Chrome DevTools Protocol:Target Domain
- Chromium 151.0.7922.138:CDP Target Handler
- Chromium 151.0.7922.138:Chrome DevTools Manager Delegate
查看本文修订记录(1)
- v1.0 · 2026.08.17 首次发布。