返回知识中心
部署

自定义构建的 Puppeteer MCP 服务器配置

把 Puppeteer MCP 服务器指向指定 Chromium 构建与加密 profile,说明扁平启动对象与 Playwright 嵌套结构为何不能互换。

BotBrowser Team

文档中心

想直接进入 部署 文档吗?

这篇文章属于知识中心的指南库。若你要步骤化配置、参考说明和持续更新,请直接进入对应 docs 分区。

Puppeteer MCP 服务器掌握浏览器启动过程,交给它的配置决定每个代理任务运行在哪个 Chromium 二进制与哪份 profile 上。Puppeteer 的 LaunchOptions 与 Playwright 的形状不同,在两者之间复制配置会静默丢掉对侧不存在的部分。故障是安静的:服务器启动,工具正常响应,浏览器仍是服务器本来会用的那个。Puppeteer 把 executablePath 记录为替代自带浏览器的路径,同时声明只保证支持自带构建,因此该选项属于显式决定,而不是透明设置。BotBrowser 从 Puppeteer 启动时使用 executablePath 加 args 中的 profile 参数,自身不发布 MCP 服务器,浏览器一侧无法修正一个从不转发这些值的服务器。

TL;DR

  • Puppeteer 的启动对象把 executablePath、args、headless、userDataDir、ignoreDefaultArgs、pipe 和 browser 放在同一层。Playwright 把同样的内容嵌在 launchOptions 下,两种形状不能互换。
  • Puppeteer 的 headless 接受 true、false 或字符串 'shell',其中 'shell' 选择旧的 headless 实现。写一个 Playwright 风格的布尔值在这里合法;整份复制 Playwright 配置则不合法。
  • executablePath 替代自带浏览器,Puppeteer 文档声明只保证自带构建可用。自定义路径属于受支持但未经证实的状态,需要回读进程来确认。
  • profile 在 args 中占独立元素,和 Playwright 下一样,因为它是浏览器参数而不是 Puppeteer 选项。
  • ignoreDefaultArgs 与 pipe 改变服务器交给进程的内容。设置 ignoreDefaultArgs: true 的服务器会丢掉 Puppeteer 本会追加的 flag,实际 argv 既不是配置里的,也不是 Puppeteer 默认的。

配置流程图显示 Puppeteer MCP 服务器从同一个扁平启动对象读取 executablePath、args 与 headless,再启动指定二进制;对比列显示 Playwright MCP 把相同取值嵌在 browser.launchOptions 下。

目录

Puppeteer 启动对象的位置

Puppeteer 的 LaunchOptions 是传给 puppeteer.launch() 的单个扁平对象。executablePath 是它的顶层成员,与 args、headless、userDataDir、ignoreDefaultArgs、pipe、browser、channel、debuggingPort 并列。该对象整体就是启动选项,因此不存在 launchOptions 包装层。

{
  "mcpServers": {
    "puppeteer": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-puppeteer"],
      "env": {
        "PUPPETEER_EXECUTABLE_PATH": "/opt/botbrowser/chrome",
        "PUPPETEER_LAUNCH_OPTIONS": "{\"headless\":true,\"args\":[\"--bot-profile=/etc/botbrowser/profile.enc\"]}"
      }
    }
  }
}

上面的例子给出服务器接收取值的两种途径:可执行文件用专用环境变量,其余内容用一段 JSON。这两个名字在 Puppeteer 系服务器之间没有统一标准,因此第一步是查服务器自己的文档,确认它读取的确切变量或 flag,再确认该值真的到达启动对象。

Puppeteer 成员类型对自定义构建的作用
executablePathstring替代自带浏览器;Puppeteer 只保证自身构建可用
argsstring[]额外命令行参数,一个数组元素对应一个参数
headlessboolean 或 'shell'true 使用新 headless 模式,'shell' 使用旧模式
userDataDirstring持久化 profile 目录,与 BotBrowser 的 profile 参数不同
ignoreDefaultArgsboolean 或 string[]丢弃 Puppeteer 默认 flag,或按名称过滤掉指定项
pipeboolean通过 stdio 而不是 WebSocket 承载协议
browserSupportedBrowser选择 Puppeteer 应期待的浏览器家族
channelChromeReleaseChannel在已知系统位置查找已安装的 Chrome,而不是使用自带二进制

自定义构建部署中有两行需要特别处理。channel 与 executablePath 都能指向一个浏览器,Puppeteer 文档把 channel 描述为在系统已知位置查找常规 Chrome 安装。两者同时设置会让同一个问题有两个答案,最终哪个生效属于实现细节,消除其中一个比测试哪个胜出更可靠。

扁平形状为何容易误读

扁平对象没有结构信号区分哪些键属于启动层、哪些属于上下文层。在 Playwright 下,放错位置的键会落进另一个对象并被忽略;在 Puppeteer 下,LaunchOptions 中不存在的键根本不属于选项,puppeteer.launch() 也没有理由报错。两条路径通向同一个静默结果:服务器启动,某一项设置从未生效。

有效的检查方式是把对象展开,与文档列出的 LaunchOptions 成员逐键对照。不在这份清单上的键就不是启动选项,无论它看起来多像。

Puppeteer 遇到不可用路径时的行为

executablePath 指向一个文件,Puppeteer 启动该文件。路径不存在、在服务器工作目录下无法解析,或文件存在但运行服务器的账户没有执行权限,启动器会报错,而不会替换成另一个二进制文件;这一点来自 Puppeteer 把给定路径交给它启动的进程,属于对启动器行为的推断,而不是 LaunchOptions 页面上的原话。这一行为值得记住,因为它与安静失败相反:存在但写错的路径会产生一个能工作却非预期的浏览器,不存在的路径会产生操作者能看到的错误。

真正隐藏的故障是路径有效却指向别处。配置缺失时替换为默认路径的服务器正好制造这种结果:可执行文件有效、浏览器能工作、没有任何报错。检查必须比较观测到的路径与预期路径,而不是只确认某个路径存在。

服务器在配置之上追加的内容

Puppeteer 系 MCP 服务器是包装层:读取自身配置、构造 LaunchOptions 对象、调用 puppeteer.launch()。服务器在配置与这次调用之间追加的内容,从外部看不见。这正是回读在此处比直接脚本更重要的原因,直接脚本里的选项对象就是操作者写下的那一份。

这种封装在 Playwright 一侧的变体,包括它的嵌套结构与静默回退到内置浏览器,见为自定义 Chromium 构建配置 Playwright MCP;代理循环与浏览器进程之间的职责划分见面向 AI Agent 的 headless 浏览器。服务器可能追加默认 args 数组、headless 默认值或 ignoreDefaultArgs 取值。追加自身参数的服务器产生的 argv 是配置与默认值的并集,顺序也不由操作者决定。读取运行中的进程是观察这个并集的唯一方式。

实际 argv 为何比配置文件更重要

两次运行可以共用同一份配置文件却启动不同浏览器,只要服务器版本在两次之间改变了默认值。开始追加新默认 flag 或停止追加某个 flag 的服务器,会在操作者文件未变的情况下改变实际 argv。浏览器读取的是 argv 而不是服务器配置,可观察行为跟随 argv。

因此实际 argv 应与观测结果一起记录,而不只是记录产生它的配置。配置加服务器版本可复现;一旦服务器能改变追加内容,单独的配置不足以复现。

与 Playwright MCP 形状的差异

两个生态用不同结构回答同一个问题,差异正好落在复制配置会失效的位置。

关注点Playwright MCPPuppeteer 启动对象
executablePath 的位置browser.launchOptions.executablePath顶层 executablePath
args 的位置browser.launchOptions.args顶层 args
headless 取值launchOptions 内的布尔 headless布尔值或字符串 'shell'
上下文级设置独立的 browser.contextOptions 对象不属于 LaunchOptions;viewport 由服务器或代码另行设置
profile 持久化browser 下的 userDataDir顶层的 userDataDir
连接已运行的浏览器cdpEndpoint 或 remoteEndpointLaunchOptions 之外的 browserWSEndpoint 或 connect()

前两行解释了为何把 Playwright MCP 的 JSON 块粘进 Puppeteer 服务器不起作用:这些键不在 Puppeteer 服务器查找的两个位置中的任何一个。第三行更隐蔽,headless: true 在两边都合法,复制品看似能用,周围结构却是错的。

上下文拆分在这里不存在

Playwright 把启动选项与上下文选项分开,viewport、locale、权限因此放在另一个对象里。Puppeteer 的 LaunchOptions 没有这种拆分:viewport 与页面级设置在启动之后通过 page.setViewport() 或设备描述符应用。期待 contextOptions 块承载 locale 的配置找不到对应字段。

任务依赖 locale 或时区时,这一差异有直接影响。Playwright MCP 下它们属于上下文,在页面存在之前配置。Puppeteer 下它们要么是 args 中的浏览器参数,要么是启动后的脚本调用,服务器必须提供其中一条路径。

仿真调用是第三条路径

page.emulate() 与 page.setViewport() 在浏览器启动后改变页面报告的内容,因此 locale、时区、viewport 还有第三个来源。通过 args 设置 locale、通过仿真调用设置另一个属性、再让服务器默认值设置第三项的负载,有三个来源而没有唯一答案。

保持可控的规则是每个属性只用一个来源。浏览器在首个请求之前必须知道的内容放进 args,页面加载之后才需要的内容用仿真调用。记录每个属性走过的路径,因为在 args 中设置 locale 的运行与通过仿真设置的运行,比较的不是同一件事。

两种不同的 profile 概念

Puppeteer 的 userDataDir 与 BotBrowser 的 profile 参数都描述持久状态,两者并不相同。userDataDir 是 Puppeteer 透传给浏览器的 Chromium 用户数据目录。BotBrowser profile 是二进制通过自身 flag 加载的加密配置文件。设置其中一个不会设置另一个,只暴露 userDataDir 的服务器没有 profile flag 的位置,除非它转发任意 args。

传递 profile 与 headless 模式

profile flag 是浏览器参数,因此作为单个元素放进 args。headless 模式是 Puppeteer 选项,因此放进 headless。形状混淆之后,下一个要检查的配置错误就是把两者放反。

{
  "executablePath": "/opt/botbrowser/chrome",
  "headless": true,
  "args": ["--bot-profile=/etc/botbrowser/profile.enc", "--bot-locale=en-US", "--no-first-run"]
}

每个元素是一个参数,不带 shell 引号。包含空格的单个字符串是一个畸形参数。例子中的第三个元素是普通 Chromium flag 而不是 BotBrowser flag,这说明 args 可以自由混合两类内容:Puppeteer 不区分它们。

引号、转义与 JSON 层

配置是 JSON 时,参数经过两层:JSON 字符串转义,然后是参数向量。C:\botbrowser\chrome.exe 这样的 Windows 路径需要在 JSON 中转义反斜杠,含引号的值需要 JSON 转义而不是 shell 转义。一层正确一层错误,产生的路径在文件里看着对,启动时却是错的。

不含空格与引号的 profile 路径绕开整类问题,短目录下的绝对路径也比深层目录中的长路径更容易逐字符核对。

headless、'shell' 与随之改变的内容

Puppeteer 文档记载 headless 接受 true 或字符串 'shell',true 选择新 headless 模式,'shell' 选择旧模式。区别不只是外观:两种模式在浏览器合成方式与哪些 API 报告什么上不同。比较渲染结果或读取媒体能力的负载应记录本次运行使用的模式,因为不同模式的两次运行不是同一个测量。

ignoreDefaultArgs 何时改变结论

ignoreDefaultArgs 接受 true 或 flag 名称数组。设为 true 时,Puppeteer 停止追加默认参数,浏览器只以 args 中的内容启动。传入数组时,指定默认项被过滤,其余保留。两者都改变实际 argv,也都可能由服务器自行设置。

运维后果是一个文档记载的 BotBrowser flag 可能被 Puppeteer 追加的默认项抵消,Puppeteer 依赖的默认项也可能被 ignoreDefaultArgs 移除。这两种情况在配置文件中都不可见,验证步骤因此读取进程而不是读取配置。

pipe 与服务器和浏览器的通信方式

pipe: true 通过标准输入输出承载自动化协议,不打开 WebSocket。把浏览器作为子进程运行的服务器因此少一个监听端口,故障模式与安全面都会变化。假定调试端口存在的配置在 pipe 打开时不成立,通过端口连接的工具无法到达管道浏览器。

记录每次运行使用的传输方式。管道上得到的结果与端口上得到的结果,只有在其余配置一致时才来自同一个浏览器,传输方式属于配置的一部分而非实现细节。

确认实际启动的二进制

验证有三个读数,回答不同问题。进程路径证明执行了哪个文件。user agent 描述页面报告的内容,自定义构建可以独立于二进制配置它。构建标记证明一个自带构建缺少的能力。

先读进程。在生成服务器的宿主机上找到子进程,把它的可执行文件与配置路径比较。未转发 executablePath 的服务器会显示 Puppeteer 缓存目录下的路径,这一次比较就能定论,不需要页面级探测。

读数能证明不能证明
子进程可执行文件服务器执行了哪个文件profile 已加载
navigator.userAgent页面报告了什么哪个二进制产生了它
构建标记某个能力存在配置按预期路径到达了它
profile 加载证据加密 profile 被接受页面内容正确

进程树不可见时

运行在容器、远程主机或沙箱中的服务器,可能不向操作者的 shell 暴露子进程。此时路径必须来自拥有进程的环境内部,通过服务器写入的日志行、/proc 条目或平台提供的诊断工具获取。要求不变:在可获得观测的位置,把观测到的可执行文件与配置比较。

这些都不存在时,页面内的构建标记是退路证据,并且应被描述为较弱。标记证明能力存在,不证明提供页面的进程就是操作者配置的那个。

记录一次验证

把配置路径、观测路径、headless 模式、实际 args 与标记结果连同时间戳存在一起。后续运行可以直接比较,不必重新推导基线。服务器更新改变默认 args 时,这条记录让变化可见。

客户端看不到的内容

MCP 客户端看到服务器发布的工具,看不到启动对象、进程树或传入的参数。从客户端一侧,配置错误的服务器与正确的服务器无法区分,因此验证必须通过会话请求,并记录在会话之外。

Puppeteer 路径的症状表

症状原因确认方式修复
浏览器启动,profile 设置无效args 缺少 profile flagargs 中没有以 profile flag 开头的元素每个 flag 增加一个数组元素
浏览器从缓存目录启动executablePath 未进入启动对象子进程路径落在 Puppeteer 缓存下把键移到启动对象顶层
服务器以启动错误退出路径缺失或不可执行该路径对服务器账户无法解析使用账户可执行的绝对路径
服务器更新后 flag 行为改变服务器改变了默认 args实际 argv 与记录不同记录 argv 并固定服务器版本
工具无法到达浏览器pipe 与端口传输不匹配配置设置 pipe,工具却期待端口每个部署只选一种传输

第二行是能躲过整个工作会话的一项。退回自带浏览器的服务器会正确回答每次工具调用,唯一证据是进程路径。

FAQ

Playwright MCP 配置能复用于 Puppeteer 服务器吗?

不能。Playwright MCP 把 executablePath 与 args 嵌在 browser.launchOptions 下,Puppeteer 要求两者位于启动对象顶层。粘贴的配置块让 Puppeteer 服务器得不到可执行文件路径,服务器于是启动自带浏览器并报告成功。

headless: 'shell' 与 headless: false 相同吗?

不同。'shell' 选择旧 headless 实现,仍然没有可见窗口,但合成方式与报告内容不同于新 headless 模式。false 运行有头浏览器。记录一次运行使用三者中的哪一个,才能让两次测量可比。

userDataDir 能替代 BotBrowser profile 吗?

不能。userDataDir 是 Puppeteer 传给浏览器的 Chromium 用户数据目录。BotBrowser profile 是二进制通过 args 中自身 flag 加载的加密配置文件。只暴露 userDataDir 的服务器没有 profile 的位置,除非它转发任意参数。

实践结论

扁平启动对象把 executablePath、args、headless 放在同一层,profile flag 作为独立元素放进 args。宿主机上的子进程可执行文件与配置路径比较之后,会话结果才具备可信依据。

下一步是一次启动验证:服务器启动后由客户端执行一次导航,再在宿主机上读取子进程路径。路径与配置一致,二进制得到证明;路径落在 Puppeteer 缓存里,说明服务器忽略了该选项,每个结果描述的都是一个未被选择的浏览器。

Sources

#Puppeteer MCP#MCP 服务器#executablePath#浏览器自动化#AI 代理

本文属于指南: 浏览器部署与发布:运维决策地图

在 BotBrowser 里跑一遍同样的检查

加载 profile,让刚读到的这些信号在浏览器内部保持一致,并在正式上线前先在自己的机器上确认结果。