跳到主要内容

数据截至 (上游 commit 7e0457a7cbf8)

配置面与护栏:三条入口、profile 隔离,和那些自称「不是安全边界」的开关

这章讲什么: 这个 server 能被怎么配、配置从哪几条路进来、浏览器身份(登录态)怎么隔离、以及它提供的几道护栏各自的真实强度。部署前该读的就是这一章。

可读性说明: config.d.ts 是本仓库唯一一份完整的「规格书」,而且它带着大量注释——本章的绝大多数论断直接锚在它上面。合并逻辑本身在上游,本章只到「对外可观察的契约」为止。


1. 三条入口,汇成一个 Config

1.1 结构图

┌──────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ 46 个 CLI 选项│ │ PLAYWRIGHT_MCP_* │ │ --config a.json │
│ --headless │ │ 环境变量(逐项对应)│ │ JSON 配置文件 │
└──────┬───────┘ └─────────┬──────────┘ └────────┬────────┘
└────────────────┬────┴───────────────────────┘

合并后的 Config 对象
(合并规则在上游,本仓库不可见)


browser_get_config 可把最终结果打印出来
(需 --caps=config)

1.2 三条入口各自的证据

入口依据
CLI 选项(46 个)README.md:409-454 生成的表格
环境变量同表每行末尾的 *env* 列,命名规则在 update-readme.js:147-153optionEnvName
配置文件--config path/to/config.json(README.md:544),schema 即 config.d.tsConfig

怎么确认合并真的发生了: browser_get_config 这个工具的描述就是「获取合并 CLI 选项、环境变量和配置文件之后的最终配置」(README.md:1142)。它要 --caps=config 才出现——把「自省配置」也当成一项需要显式开启的能力。

1.3 环境变量名的生成规则

update-readme.js:147-153 那个 12 行函数,是文档里那一整列环境变量名的来源:

function optionEnvName(prefix) {
if (prefix === 'secrets') return 'PLAYWRIGHT_MCP_SECRETS_FILE';
if (prefix === 'cdp-header') return 'PLAYWRIGHT_MCP_CDP_HEADERS';
return `PLAYWRIGHT_MCP_` + prefix.toUpperCase().replace(/-/g, '_');
}

规则是「统一大写下划线化」,只有两个手工特例:--secrets..._SECRETS_FILE(强调它是文件路径而非值),--cdp-header..._CDP_HEADERS(单数选项对应复数变量,因为可以传多次)。

可借鉴之处: 规则化 + 少量白名单例外,比逐个手写映射表更抗漂移——新增选项自动获得正确的环境变量名和文档行。


2. Config 的骨架

config.d.ts:33-250 定义的 Config 分成这么几块:

顶层字段管什么行号
browser浏览器种类、profile、启动/上下文选项、CDP/远程接入、初始化脚本config.d.ts:37-103
extension连接到已运行的 Edge/Chrome(设了就忽略 browser)config.d.ts:105-110
serverHTTP 模式的 port / host / allowedHostsconfig.d.ts:112-128
capabilities开哪些工具组config.d.ts:130-137
saveSession / sharedBrowserContext会话落盘、HTTP 客户端间共享上下文config.d.ts:139-147
secrets响应中的敏感文本替换config.d.ts:149-154
outputDir / outputMaxSize输出目录与淘汰阈值config.d.ts:156-164
console / networkconsole 级别、origin 允许/阻止清单config.d.ts:166-191
testIdAttributetest id 属性名,默认 data-testidconfig.d.ts:193-196
timeoutsaction / navigation / expect / settle 四个超时config.d.ts:198-218
imageResponses / snapshot图片是否回传、快照模式与包围盒config.d.ts:220-236
allowUnrestrictedFileAccess文件访问护栏config.d.ts:238-244
codegen回执代码用哪种语言config.d.ts:246-249

2.1 四个超时,各管一段

这四个值得单独列出来,因为它们直接决定 agent 的体感:

字段默认管什么行号
timeouts.action5000ms单次动作config.d.ts:199-202
timeouts.navigation60000ms导航config.d.ts:204-207
timeouts.expect5000ms断言config.d.ts:209-212
timeouts.settle500ms每次动作后,等被触发的导航/请求安定下来再响应config.d.ts:214-217

settle 是这里最有意思的一个:它不是「等某个条件」,而是一段固定的静默窗口,加在每次动作的尾巴上。这是为什么模型点完按钮后拿到的快照通常已经是新页面——不需要它自己插一条 browser_wait_for。代价是每次动作至少多 500ms。

2.2 CLI 选项与 Config 字段不是一一对应

这是配置这一块最容易踩的坑。举几个实例:

CLI 选项Config 里的落点
--headless--executable-path--proxy-server--no-sandbox都进 browser.launchOptions(config.d.ts:54-60 注释点名了 channel/headless/executablePath)
--viewport-size--user-agent--device--grant-permissions都进 browser.contextOptions(config.d.ts:62-67)
--endpoint对应 browser.remoteEndpoint(config.d.ts:84-91)
--storage-stateConfig没有同名字段;README 说它把 cookie 和 localStorage 载入隔离上下文(README.md:514)
--secrets <path>选项是文件路径,Config.secretsRecord<string,string>(config.d.ts:154)

结论: 想知道某个 CLI 选项能配什么值,看 README.md:409-454;想知道配置文件能写什么,看 config.d.ts。两者不是同一张表,不要互相推导。

2.3 remoteEndpoint 的双形态

config.d.ts:84-91 的注释说明它既可以是一个 WebSocket URL 字符串,也可以是一个对象——对象形式镜像测试运行器的 connectOptions 形状,其中 exposeNetworkheadersslowMotimeout 会被转发给底层的 connect 调用。

这是典型的「简单情况给字符串,复杂情况给对象」的 API 设计,而且刻意复用了 Playwright 测试运行器里用户已经熟悉的形状。


3. 浏览器身份:三种 profile 模式

这是配置里最影响实际体验的一块——agent 是不是带着你的登录态在跑

3.1 三种模式对比

模式登录态怎么开依据
持久 profile(默认)跨会话保留什么都不做,或 --user-data-dirREADME.md:462-481
隔离每次全新,关闭即丢--isolated,可配 --storage-state 载入初始态README.md:483-503
浏览器扩展直接用你正在开的那个浏览器的标签页--extensionREADME.md:505-507config.d.ts:105-110

3.2 持久 profile 的目录名藏着隔离机制

默认路径形如(README.md:468-476):

Windows %USERPROFILE%\AppData\Local\ms-playwright\mcp-{channel}-{workspace-hash}
macOS ~/Library/Caches/ms-playwright/mcp-{channel}-{workspace-hash}
Linux ~/.cache/ms-playwright/mcp-{channel}-{workspace-hash}

{workspace-hash} 来自 MCP client 的 workspace 根目录,README 明说这是为了「不同项目自动拿到各自的 profile」(README.md:478)。

代价写在紧跟的 IMPORTANT 块里(README.md:481): 一个持久 profile 同一时刻只能被一个浏览器实例使用,所以共享同一 workspace 的多个并发 MCP client 会互相冲突。解法是给每个额外的 client 加 --isolated 或指一个不同的 --user-data-dir

这条在多 agent 并行的场景里几乎必然会撞上,值得提前记住。

3.3 workspace roots 是怎么传进来的

{workspace-hash} 的来源在测试里能看到形状。tests/fixtures.ts:95-104 里,测试 client 声明 capabilities: { roots: {} } 并注册 ListRootsRequestSchema 的处理器,server 会回调过来问「你的 workspace 根目录是什么」:

client.setRequestHandler(ListRootsRequestSchema, async request => {
if (options.rootsResponseDelay)
await new Promise(resolve => setTimeout(resolve, options.rootsResponseDelay));
return { roots: options.roots };
});

注意那个 rootsResponseDelay——测试专门留了一个「故意延迟回答 roots」的开关。这说明上游存在「server 启动时等待 client 回 roots」的时序,而且这个时序脆弱到值得专门参数化测试。

roots 不只用于 profile 命名,也是文件访问护栏的基准(见 §4.3)。

3.4 给页面注入初始状态的两条路

选项文件类型跑在哪依据
--init-pageTypeScriptPlaywright 的 page 对象上,Node 侧README.md:519-528
--init-scriptJavaScript每个页面里,先于页面自身脚本README.md:530-535

两者的差别是在哪一侧执行--init-page 的示例是给上下文授权地理位置、设定坐标、设视口(README.md:521-527)——这些都是 Playwright API 层的操作。--init-script 的示例则是 window.isPlaywrightMCP = true(README.md:533-535)——页面内的全局变量。

用途区分: 要改浏览器/上下文行为,用 --init-page;要打桩页面 API(覆盖 Datefetch、注入标记),用 --init-script


4. 四道护栏,以及它们的真实强度

这一节是本章的重点。这个项目在护栏问题上异常坦诚——几乎每一道护栏旁边都写着「这不是安全边界」

4.1 总纲

README 有一节 ## Security,正文只有一句话(README.md:800):Playwright MCP 不是一个安全边界,并把读者指向 MCP 官方的安全最佳实践。

把这句话当成读下面四条的前提。

4.2 secrets:防误看,不防攻击

config.d.ts:149-154 的注释原文大意是:secrets 用于把工具响应里匹配到的明文替换掉,以免 LLM 意外拿到敏感数据;它是一个便利功能而不是安全特性,务必始终在 client 侧检查进出工具的信息。

所以它的定位很清楚:降低「密码顺手进了模型上下文」这类事故概率,而不是对抗一个想拿到密钥的攻击者。CLI 侧对应 --secrets <path>,传的是一个 dotenv 格式的文件(README.md:447)。

4.3 文件访问:防走神,不防越权

config.d.ts:238-244 的注释同样直白:allowUnrestrictedFileAccess 作为护栏是为了防止 LLM 意外漫游到预定工作区之外;它是「捕捉非预期文件访问的便利防线,不是安全边界」,刻意绕开很容易,真正的安全要靠 client 层权限。

默认行为在 CLI 帮助里(README.md:411):文件系统访问被限制在 workspace root 目录内(没配 roots 就是 cwd),并且禁止导航到 file:// URL。加上这个开关就两条都放开。

呼应上面 §3.3:workspace roots 同时是 profile 命名的输入和文件访问的边界。

4.4 网络 origin:阻止清单先跑,而且不管重定向

两个选项的语义在帮助文本里说得很细:

  • --allowed-origins:分号分隔的受信任 origin 清单,默认全放行(README.md:410)
  • --blocked-origins:分号分隔的阻止清单,先于允许清单求值;单独用阻止清单时,不匹配的请求仍然放行(README.md:412)

配置文件侧的注释还补了一条求值规则:同时匹配允许和阻止清单的 origin 会被阻止(config.d.ts:175config.d.ts:184),并给出了支持的两种格式——完整 origin https://example.com:8080,以及通配端口 http://localhost:*

判定顺序图:

请求 origin


匹配 blockedOrigins ? ──是──► 阻止
│否

配了 allowedOrigins ? ──否──► 放行(默认全放)
│是

匹配 allowedOrigins ? ──是──► 放行
│否

阻止

两条硬限制写在帮助文本里(README.md:410README.md:412):不是安全边界,并且不影响重定向。第二条尤其要紧——一个被允许的 origin 把你 302 到别处,清单管不着。

4.5 HTTP 模式:allowedHosts 防的是 DNS rebinding

config.d.ts:123-127 的注释特意澄清:allowedHosts 列的是「这个 server 允许从哪些 host 提供服务」,默认是它绑定的 host,这不是为了 CORS,而是为了 DNS rebinding 防护

CLI 侧还提供了一个总开关:传 '*' 可以整个关掉 host 检查(README.md:409)。

这道护栏和前三道性质不同——它是真的在防一类具体攻击(恶意网页把域名解析到 127.0.0.1 来访问你本机的 server),不是「便利防线」。用 --host 0.0.0.0 把 server 暴露到局域网时,这一项必须一起考虑。

4.6 汇总

护栏防什么项目自评依据
secrets密钥意外进模型上下文便利,非安全特性config.d.ts:149-154
allowUnrestrictedFileAccessLLM 误读工作区外文件便利,非安全边界config.d.ts:238-244
allowed/blockedOrigins浏览器访问非预期站点非安全边界,不管重定向README.md:410-412
server.allowedHostsDNS rebinding明确的防护目的config.d.ts:123-127

再叠加一条来自 02 章 的事实: browser_run_code_unsafe 默认就在工具列表里,而它自己的描述说等价于 RCE(README.md:1044)。任何关于这个 server 权限面的评估,都必须把这一项算进去。


5. 输出目录:一个带淘汰策略的落盘区

很多工具都能把结果写文件而不是塞进响应(browser_snapshotbrowser_evaluatebrowser_console_messagesbrowser_network_request(s)browser_take_screenshot)。这些文件的去处由两个配置管:

  • outputDir —— 输出目录(config.d.ts:156-159)
  • outputMaxSize —— 淘汰旧输出文件的阈值,单位字节(config.d.ts:161-164)

有淘汰阈值这件事本身说明:上游把输出目录当作一个有上限的缓存,而不是无限增长的日志区。长跑的 agent 会话不会把磁盘写爆。

Dockerfile:63-64 的注释从另一侧印证了这个机制:工作目录必须可写,因为 MCP 可能需要在里面创建默认输出目录。

另外 saveSession(config.d.ts:139-142)可以把整个 Playwright 会话存进输出目录;.gitignore:7 里那行 sessions/ 就是给它准备的。


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

  • 合并优先级。 CLI、环境变量、配置文件三者谁覆盖谁,本仓库没有代码可读,README 也没写。运行期只能靠 browser_get_config 查最终结果。
  • CLI 选项到 Config 字段的完整映射。 §2.2 列的几条来自注释和描述文本的对应关系,不是从合并代码读出来的。
  • origin 匹配的实现细节。 通配端口之外还支持什么、是否匹配子域,注释只给了两种格式示例。
  • --caps 的完整取值。 帮助文本只列 vision, pdf, devtools(README.md:415),但 README 生成的工具目录里还有 config/network/storage/testing 四组(README.md:1136114911941547),ToolCapability 类型也列了全部 12 个值(config.d.ts:19-31)。帮助文本落后于实际能力集。

7. 代码地图

主题文件路径符号名 / 锚点
配置总规格config.d.tsConfig
capability 枚举(12 个)config.d.tsToolCapability
浏览器与 profileconfig.d.tsbrowser.browserNameisolateduserDataDirlaunchOptionscontextOptions
远程/CDP 接入config.d.tscdpEndpointcdpHeaderscdpTimeoutremoteEndpoint
页面初始化config.d.tsinitPageinitScript
HTTP server 与 rebinding 防护config.d.tsserver.portserver.hostserver.allowedHosts
四个超时config.d.tstimeouts.actionnavigationexpectsettle
origin 清单config.d.tsnetwork.allowedOriginsnetwork.blockedOrigins
敏感文本替换config.d.tssecrets
文件访问护栏config.d.tsallowUnrestrictedFileAccess
输出目录与淘汰config.d.tsoutputDiroutputMaxSizesaveSession
回执代码语言config.d.tscodegen
46 个 CLI 选项表README.md<!--- Options generated by update-readme.js -->
环境变量命名规则update-readme.jsoptionEnvName
profile 路径与并发冲突README.md### User profile 小节
初始状态两条路README.md### Initial state 小节
安全总纲README.md## Security 小节
workspace roots 时序tests/fixtures.tsListRootsRequestSchemarootsResponseDelay
配置文件如何被测试注入tests/fixtures.tsstartClient 中写 config.json 并追加 --config=