Skip to main content
Glama
chuanxu742-glitch

antigravity-browser

浏览器 Profile 隔离工作台与策略约束自动化 MCP

License

产品定位:面向自有、测试或已获授权网站的本地浏览器环境隔离、持久 Profile 管理与可审计自动化。项目不承诺绕过站点挑战、规避风控、账号不受限制或达到第三方检测站分数。

当前已交付核心能力

  1. 持久 Profile:Firefox/Chromium 环境配置、稳定种子、Cookie 与存储目录生命周期管理;

  2. 本地 Studio:Profile 创建、更新、克隆、批量启停、可恢复删除,持久代理池、受管扩展中心、真实出口验证、标签轮换以及带条件/循环/变量/重试/产物的声明式 RPA;

  3. 本地安全:REST Bearer/HttpOnly Cookie 鉴权、工作区成员、资源级授权、可撤销哈希 API Key、结构化审计,以及 Cookie、代理密码和 2FA Secret 的 AES-256-GCM 加密;Windows 默认使用 DPAPI 保护 Studio 主密钥;

  4. 策略网关:标准 MCP 与 Local REST API 只组合高层动作,限制会话资源、URL 范围和自动化状态。

检测到挑战时,自动化立即暂停。生产挑战的标准结果是 CHALLENGE_DETECTED/SESSION_PAUSED_CHALLENGE,等待受信任人员处理;服务不会刷新、重试、切换环境或与挑战控件交互。

Service Worker 身份链路:Chromium 会通过仅绑定回环地址的浏览器级 CDP 通道,在网站代码执行前暂停目标,统一 UA、平台、Client Hints 与后续网络请求;Firefox 不依赖页面脚本注入,而是使用 Gecko 原生 Profile 首选项统一 Window、Worker、Service Worker 和 HTTP 通道中的 UA、平台、appVersion、语言、硬件并发数及相关请求头。stock Firefox 的 Playwright 时区覆盖不会进入 Service Worker,因此完整时区覆盖仍只在 browser-core/firefox 的版本锁定内核补丁中提供,产品能力接口对此保持 false,不会把部分覆盖冒充为全覆盖。

安装

要求 Node.js 20 或更高版本。

npm ci
npm run install:firefox
npm run build

启动本地桌面 Studio:

npm run studio

首次启动会在 data/ 创建本机主密钥和 owner token,并通过一次性启动链接写入 HttpOnly Cookie。Windows 上两个启动机密以当前用户 DPAPI 密文保存,旧明文启动文件会自动迁移;业务密文仍使用 AES-256-GCM。可用 STUDIO_MASTER_KEYSTUDIO_ACCESS_TOKENSTUDIO_USERS_JSON 接入外部 KMS 或配置静态多角色凭据。data/ 必须作为敏感目录备份与保护;主密钥丢失后已有密文无法恢复。

Studio 产品 API

Studio API 默认只监听 127.0.0.1,除 /api/v1/health 外均需认证。主要端点包括:

  • /api/v1/openapi.json:OpenAPI 3.1 入口;Profile 列表支持 qoffsetlimit,总数返回在 X-Total-Count

  • /api/v1/profiles/trash/profiles/{id}/restore/profiles/{id}/purge:回收站、恢复与仅 owner 可用的永久清除。

  • /api/v1/team/*:工作区、成员、资源 grants 以及只在创建时返回明文的可撤销 API Key。

  • /api/v1/extensions/extensions/import/profiles/{id}/extensions:仅 owner 可导入的受管 ZIP/XPI 仓库,以及按 Profile 分配扩展。

  • /api/v1/migration/local-browsers/migration/import-local:仅 owner 可用的本机 Chrome、Edge、Firefox Profile 扫描与网站会话迁移。

  • /api/v1/sessions/{sessionId}/wsabs-rpc/1 有界 WebSocket 自动化协议,支持 status/open/click/type/select/scroll/snapshot/screenshot;它不是原始 CDP。

  • /api/v1/synchronizer/captures:显式启停主窗口动作捕获。只同步可解析的语义目标,密码字段被排除。

  • /api/v1/product/capabilities/product/runtime-health/metrics:真实能力边界、外部云运行时适配器状态和本机运行指标。

小状态文件使用同目录临时文件、fsync、原子替换和 .bak 上一版本恢复。该方案为单 Studio 进程设计,不等于支持多个进程同时写入,也不等于已经提供云数据库。云浏览器与 Android 云手机提供标准 provider 适配边界,以及经过鉴权的创建、停止和健康检查 API;未注册并配置真实供应商与凭据时,能力接口会返回未配置,不会创建模拟设备或伪造连接地址。

本机浏览器数据导入

Studio 的“数据迁移与互通”页面可以扫描系统默认位置中的 Chrome、Edge 和 Firefox Profile。导入前必须完全退出源浏览器;服务只接受扫描产生的 sourceId,不接受调用方提交任意本机路径。迁移内容包括 Cookie 数据库、Local/Session Storage、IndexedDB、Service Worker 存储和必要的站点权限数据。密码库、历史记录、书签、自动填充和源浏览器扩展不会复制,符号链接与锁文件也会跳过。

Chromium 的 Cookie 可能受 Windows DPAPI 或 App-Bound Encryption 保护。导入器会复制最小化的 os_crypt 元数据,但不会绕过操作系统保护;如果项目锁定的 Chromium 无法解密,其他站点存储仍会保留,登录 Cookie 需要在导入后的持久 Profile 中手动重新建立。Firefox Cookie 数据库和 origin storage 会复制到 Firefox Profile 根目录。

也可以先从命令行只扫描,再按 sourceId 导入:

npm run import:local
npm run import:local -- <sourceId> confirm-browser-closed

代理状态分为 unhealthyreachableverifiedreachable 只代表代理端口可达;只有请求确实通过代理并从出口服务取得 IP 时才是 verified。国家字段只在所配置的出口服务真实返回国家信息时出现,不使用默认国家填充。

开发环境没有 package-lock.json 时可用 npm install 代替 npm cinpm run install:browsers 下载项目锁定的 Firefox 与 Chromium;只使用单一引擎时可分别运行 npm run install:firefoxnpm run install:chromium。启动前复制 .env.example,至少设置 BROWSER_ALLOWED_HOSTS

# POSIX
cp .env.example .env
export BROWSER_ALLOWED_HOSTS='test.example.com,*.staging.example.com'
npm start
# Windows PowerShell
Copy-Item .env.example .env
$env:BROWSER_ALLOWED_HOSTS = 'test.example.com,*.staging.example.com'
npm start

如果当前网络通过 Meta Tunnel 将已批准的公网域名解析到 198.18.0.0/15 的合成地址,管理员可显式设置 BROWSER_ALLOW_SYNTHETIC_TUNNEL=true。该开关只放行 198.18.0.0/15,仍然拒绝 RFC1918、回环、链路本地和云元数据地址;默认关闭,不应替代正常公网 DNS。

当前版本的服务仍只从环境和 Studio 管理面读取管理员配置;MCP 调用不能传入 allowlist、浏览器可执行文件、扩展包、扩展路径或 profile 路径,但 browser_start 支持按会话传入代理、指纹、GeoIP、语言、时区、地理位置、UA、视口和种子。自定义 UA 的浏览器品牌与主版本必须匹配项目锁定的受管内核,并且只能与受管指纹同时使用;留空时服务会自动生成兼容值。Chromium 的 JS Client Hints 与网络 Sec-CH-UA-* 由同一版本/OS 模型配置,外部 CDP 浏览器禁止注入受管指纹。扩展只能由 Studio owner 导入受管仓库,并通过服务器生成的扩展 ID 分配给持久 Profile。

受管扩展中心

Studio 的“扩展中心”接受最大 12 MiB 的 ZIP/XPI,导入时检查压缩路径、解压大小、文件数量、Manifest V2/V3、危险权限和可执行载荷,并固定原包及解压内容的 SHA-256。nativeMessagingdebuggermanagement 权限被拒绝;Cookie、代理、历史、下载、剪贴板和 <all_urls> 等高风险权限必须由 owner 显式确认。启动前会再次校验完整性,调用方不能提交任意本机路径。

Chromium 使用服务器受管的解压目录加载扩展;带扩展的 Chromium Profile 必须以 headed 模式启动。Firefox 只接受同时声明固定 Gecko ID 且包内具有 Mozilla 签名结构的 XPI,安装后仍由 Firefox 原生签名校验作最终裁决;服务不会关闭签名要求。扩展的启停或分配在停止并重新启动 Profile 后生效。

默认情况下每个会话使用临时 Profile,停止或到期后清理。若确实需要保留登录态,管理员可将 BROWSER_PERSIST_PROFILES=true,并在 browser_start 中使用安全的 profile 名称;Profile 会保存到 BROWSER_DATA_DIR/profiles/<profile>,因此该目录必须挂载到受保护的持久化卷。服务 不会把同一个持久化 Profile 同时分配给多个活动会话。该开关会持久化 Cookie 和本地存储, 不应在共享主机或未加密卷上启用。

Chromium、Bridge 与控制面

stdio MCP 仍是默认的策略网关;需要浏览器池或远程 Agent 时,先构建并启动独立控制面:

npm run build
CONTROL_PLANE_TOKEN='use-a-long-random-secret' npm run start:control-plane

控制面默认监听 127.0.0.1:8081,提供 /api/browsers/api/bindings/api/platforms/api/skills/ws/agents。设置 CONTROL_PLANE_TOKEN 后,HTTP 使用 Authorization: Bearer ...,WebSocket 可使用同一 Bearer 或查询参数 ?token=...。 Agent 通过 WS 反向连接后可以调用受策略约束的 MCP 工具;这适合 NAT/内网环境。

浏览器池中的 managed 实例由服务启动 Firefox 或 Chromium,cdp 实例通过 cdpEndpoint 接管已有 Chromium,bridge 实例等待 OpenCLI 扩展连接到 /ws/bridge/<browserId>。Bridge 使用带 requestId 的有界 RPC,不开放任意 JavaScript;扩展 侧实现 antigravity-bridge.v1 协议并继续执行页面挑战暂停与人工确认流程。实例可以通过 PATCH /api/browsers/<id> 在停止状态切换模式,并通过 /api/bindings 将站点绑定到指定实例。

仓库现已自带 browser-bridge-extension Manifest V3 扩展。它参考 OpenCLI/Razormind 的“扩展 + 本机守护进程 + chrome.debugger”架构,直接使用常用 Chrome 中 已经登录的页面,不复制 Cookie,也不受 Chrome App-Bound Encryption 的跨内核解密限制。 扩展只允许连接 localhost/127.0.0.1,控制面只接受 navigate、语义快照、点击、输入、 选择、滚动、截图和受限标签页管理;没有原始 Cookie、任意 JavaScript 或任意 CDP 方法入口。

构建并启动控制面后,可生成/复用本机 Chrome Bridge 配置:

npm run build
CONTROL_PLANE_TOKEN='use-a-long-random-secret' npm run start:control-plane
# 另开一个终端,使用相同 Token:
CONTROL_PLANE_TOKEN='use-a-long-random-secret' npm run bridge:setup

随后打开 chrome://extensions,启用开发者模式并“加载已解压的扩展程序”,选择命令输出的 绝对目录,在扩展弹窗中填写 endpoint、browserId 和 Token,再在已登录网站点击“绑定当前标签页”。 Chrome 显示“正在调试此浏览器”的提示属于 chrome.debugger 的正常安全提醒。控制面还提供 POST /api/browsers/<id>/call 供受信任的本机客户端调用上述受限操作。 例如把 noVNC 中已登录的 Chromium 接管到池中:

curl -X POST http://127.0.0.1:8081/api/browsers \
  -H "Authorization: Bearer $CONTROL_PLANE_TOKEN" -H 'content-type: application/json' \
  -d '{"name":"account-a","engine":"chromium","mode":"cdp","cdpEndpoint":"http://127.0.0.1:9222","profileName":"account-a"}'

需要可视化登录/扫码时,可选启动 Chromium + noVNC companion:

docker compose -f docker-compose.browser.yml up -d

此时 noVNC 在 http://127.0.0.1:6080,CDP 默认在 127.0.0.1:9222。该 compose 使用独立 chromium-profile 卷保存登录态;生产环境应固定经过审计的镜像版本,并将端口置于鉴权反向 代理后。/api/skills 中的动作包目前是可发现目录,带第三方 API Key 的条目会明确标注, 不会伪装成已实现的站点专用选择器。

本地控制面默认允许持续每秒 20 次工具调用、短时突发 40 次,可由管理员通过 MCP_RATE_PER_SECONDMCP_BURST 收紧。限流是单进程安全阀;多实例租户配额仍应由共享网关或 Redis 层统一实施。控制面只审计工具名、阶段、结果和 traceId,不记录调用参数、token、URL、目标名称或输入文本。

每个浏览器会话都有服务端强制的绝对生命周期。BROWSER_SESSION_TTL_MS 未设置时跟随 管理员选择的自动化策略:strict 为 30 分钟、standard 为 2 小时、trusted-local 为 24 小时;显式 TTL 只能收紧策略上限,接受范围仍为 1 分钟至 24 小时。TTL 到期后 服务会停止并清理会话、释放并发槽,后续状态或动作访问返回稳定的 SESSION_EXPIRED。 TTL 不会因导航、动作或人工接管而续期。

BROWSER_AUTOMATION_POLICY 只由管理员通过环境变量选择,默认 standard,模型不能在 工具调用中提升限制。策略同时控制每会话 Tab 数、workflow 步数/时长/结果、滚动档位、 Snapshot 历史容量/保留时间/对象大小和 retained workspace TTL:

策略

Tab

workflow

紧凑 Snapshot

workspace

strict

5

10 步 / 30 秒 / 64 KB

32 份 / 10 分钟 / 256 KB

24 小时

standard

12

50 步 / 2 分钟 / 256 KB

64 份 / 30 分钟 / 512 KB

7 天

trusted-local

20

100 步 / 5 分钟 / 1 MB

256 份 / 24 小时 / 1 MB

7 天

服务仍保留不可被策略提升的硬上限:32 Tab、100 步、5 分钟、1 MB workflow 结果、 20 个滚动档位、256 份 Snapshot、24 小时 Snapshot TTL、16 MiB 历史和 4 MiB 单对象。

Related MCP server: MCP Selenium Grid

allowlist 与网络策略

BROWSER_ALLOWED_HOSTS 必填,逗号分隔,支持精确域名和显式 *.example.com 单标签通配符;裸 *、URL、端口和空项都会导致启动失败。默认只允许 HTTPS,默认阻止环回、RFC1918 私网、链路本地、保留地址和云元数据地址;DNS 解析后的地址和重定向仍会再次检查。

page_fetchcluster_* 任务也使用同一套服务端 URL 策略。轻量抓取仅允许 GET/HEAD,不能提交 body 或任意请求头;重定向逐跳检查且有数量上限,响应体也有大小上限。响应头使用服务端白名单,只返回缓存与内容元数据;Set-Cookie、认证挑战头和其他未批准响应头不会返回给 MCP 调用方或写入集群任务结果。

page_fetch 的 HTTP(S) 连接会复用策略在本次请求中解析并批准的地址,并把该地址固定到 TCP socket;原始主机名仍用于 Host 和 HTTPS SNI。浏览器 Playwright 的导航/资源路由可以在连接前 fail-closed 地检查 allowlist、DNS 和私网地址,但无法在应用层把 Firefox 的每条底层 TCP 连接可靠地 pin 到该解析结果。因此生产部署必须在进程或容器出口使用只允许批准目标的 egress firewall 或显式代理,并在网络层阻断环回、RFC1918、链路本地、云元数据及未批准目标;应用层检查不能替代该出口控制。

仅在自有本地 fixture 且经过评审时,才在服务端显式设置 BROWSER_ALLOW_HTTP=trueBROWSER_ALLOW_PRIVATE_NETWORK=true。私网开关打开时会写 stderr 警告;示例配置不会打开它,也不给任何私网地址。

通用生产出口验收

使用环境变量驱动,不把业务域名写入代码:

$env:BROWSER_ALLOWED_HOSTS = 'shop.example.com,static.shop.example.com'
$env:ACCEPTANCE_TARGET_URLS = 'https://shop.example.com/health,https://static.shop.example.com/'
$env:ACCEPTANCE_EXPECTED_EGRESS_IPS = '203.0.113.10'
$env:ACCEPTANCE_EGRESS_IP_URL = 'https://你的出口探针域名/ip'
npm run acceptance:egress

脚本只执行无登录 GET、策略预检和手动检查重定向,不跟随未授权重定向,不读取响应正文。未设置 ACCEPTANCE_TARGET_URLS 时仅执行应用层策略矩阵;未设置预期出口 IP 时出口检查标记为 SKIP,不会冒充生产网络验收。使用 npm run acceptance:egress -- --fixture 可在无生产域名时执行确定性的 allowlist、HTTPS、私网和元数据阻断验收。

MCP 配置

mcp-config.example.json 是可复制的最小示例。将 args 改成生成后的绝对路径,并替换为自己管理的测试域名。Windows 配置:

{
  "mcpServers": {
    "compliant-firefox": {
      "command": "node",
      "args": ["C:\\path\\to\\antigravity-browser\\dist\\index.js"],
      "env": {
        "BROWSER_ALLOWED_HOSTS": "test.example.com",
        "BROWSER_ALLOW_PRIVATE_NETWORK": "false"
      }
    }
  }
}

POSIX 配置:

{
  "mcpServers": {
    "compliant-firefox": {
      "command": "node",
      "args": ["/opt/compliant-firefox/dist/index.js"],
      "env": {
        "BROWSER_ALLOWED_HOSTS": "test.example.com",
        "BROWSER_ALLOW_PRIVATE_NETWORK": "false"
      }
    }
  }
}

stdio 的 stdout 只承载 MCP 协议帧;启动错误、清理错误和私网警告写 stderr。不要把调试输出重定向到 stdout。

工具

服务注册以下 40 个标准 MCP 工具:

工具

作用

browser_start

启动受策略约束的 headless/headed Firefox 会话

browser_status

查询状态、页面摘要、挑战状态和最近的安全阻断事件

browser_environment_diagnostics

只读检查 User-Agent、平台、语言、时区、Viewport、硬件、WebGL 与 navigator.webdriver 一致性;不返回页面正文、Cookie 或凭据

browser_stop

停止并清理会话(可重复调用)

browser_reopen_headed

仅在暂停状态请求人工接管窗口

browser_resume

humanConfirmed: true 后重新检查并恢复

browser_handoff

将会话切换到 headed 人工控制并签发短期一次性 lease token

browser_takeover

用 lease token 和显式人工确认把控制权交还自动化

page_fetch

使用受 URL 策略约束的轻量 HTTP 客户端读取页面(仅 GET/HEAD)

page_open

打开 allowlist 内的绝对 HTTP(S) URL

page_snapshot

返回带 snapshotId/pageRevision 的有界语义摘要,支持 compact 输出

page_extract

结构化批量抽取页面列表/表格数据(根据 Schema 批量提取)

page_screenshot

返回内嵌图像与不透明 artifactRef,不暴露主机路径

page_click

按自适应语义目标点击唯一目标

page_type

按自适应语义目标输入有界文本,可选择清空/提交

page_select

valuelabel 选择原生选项

page_scroll

以有界档位滚动

page_wait

等待短时长或安全语义条件

page_workflow

按管理员策略串行执行声明式高层步骤,遇中断立即停止

workspace_list

列出当前活动浏览器工作区

workspace_get

查询工作区控制权与保留策略

workspace_handoff

将工作区交给人工并签发短期 lease

workspace_resume

经人工确认后恢复 Agent 控制

page_workflow_execute

执行有界声明式步骤并返回停止原因/快照

page_list_tabs

列出会话中的受控标签页

page_switch_tab

切换当前活动标签页

page_close_tab

关闭指定标签页

browser_capabilities

返回工具、限制与明确禁止的底层能力

cluster_submit_task

提交异步爬取/渲染任务至分布式优先级队列(支持轻重双模)

cluster_batch_submit

批量提交爬取任务到集群调度队列(支持并发与重试控制)

cluster_status

查询集群 Worker 节点状态与队列统计

cluster_get_task

taskId 查询分布式任务执行状态与抽取结果

cluster_list_tasks

projectIdrunId、状态和租户筛选任务,便于观察一次爬取运行

提交任务时可带稳定的 projectId / runId(仅允许字母、数字、., _, -,最多 64 字符),随后用 cluster_list_tasks 查询某次运行;查询仍按租户隔离并受既有 URL allowlist、重试、租约和审计策略约束。

语义目标示例:

{
  "sessionId": "ses_example_1234",
  "target": { "role": "button", "name": "保存", "exact": true }
}

page_snapshot 返回的短期 opaque ref 也可直接使用,例如 {"target":{"ref":"ref_..."}};页面导航或 DOM 身份变化后 ref 会失效,需重新快照。语义目标会在服务端通过有界快照解析,并且匹配不唯一时拒绝执行。

一个最小流程(具体 sessionIdbrowser_start 返回):

browser_start({"headless":true,"viewport":{"width":1280,"height":800})
page_open({"sessionId":"ses_...","url":"https://test.example.com/login"})
page_snapshot({"sessionId":"ses_..."})
page_type({"sessionId":"ses_...","target":{"label":"邮箱"},"text":"qa@example.com"})
page_click({"sessionId":"ses_...","target":{"role":"button","name":"继续"}})
page_screenshot({"sessionId":"ses_..."})
browser_stop({"sessionId":"ses_..."})

动作会经过服务端可见性、唯一性、可操作性、URL 和挑战门禁检查。页面改变后应重新 page_snapshot;不要猜测或复用失效目标。

Snapshot v2 与写动作防重

page_snapshot 在保留原有结构化字段的基础上返回 snapshotIdpageRevisioncontentcontentBytes 和明确的截断状态。设置 format: "compact" 时,响应只保留模型可直接阅读的紧凑 content,不会重复携带正文和 target 数组;maxBytes(100–4 MiB)限制紧凑内容的 UTF-8 字节数,实际值仍受当前管理员策略限制。

page_snapshot({"sessionId":"ses_...","format":"compact","maxBytes":8000})
page_snapshot({"sessionId":"ses_...","sinceSnapshotId":"snp_..."})

传入 sinceSnapshotId 时,服务从当前会话、当前 Tab 的有界内存历史中返回 changesaddedremovedupdated 和 revision 变化)。历史容量、保留时间和对象大小跟随管理员策略;只保存脱敏语义节点和文本摘要,不保存页面正文、URL、标题或 compact content。未知、过期或来自其他 Tab 的基线分别返回 SNAPSHOT_NOT_FOUND/SNAPSHOT_EXPIRED。Snapshot 与写动作应同时记录返回的 tabId

page_openpage_clickpage_typepage_selectpage_scrollpage_workflow 可选携带:

  • actionId:UUID。同一会话内,同 ID、同参数的重试直接复用首次 Promise/结果,不会重复写;同 ID、不同参数返回 ACTION_ID_CONFLICT

  • expectedPageRevision:写动作开始前必须与当前 revision 一致,否则返回可重试的 PAGE_REVISION_MISMATCH,调用方应重新快照并使用新的 actionId。

  • expectedTabId:写动作开始前必须仍在同一受控 Tab;切换 Tab 后即使 revision 数值相同也返回 PAGE_REVISION_MISMATCH

幂等缓存按会话隔离,最多保留 256 项、TTL 10 分钟,停止会话时清空。缓存只保留 SHA-256 参数摘要和安全结果,不持久化输入正文。

声明式 workflow

page_workflow 只接受 openclicktypeselectscrollwaitsnapshot。它不接受循环、变量、表达式、JavaScript、CSS/XPath、raw selector 或协议命令。workflow 步数、总时长、结果大小和单个 Snapshot 大小跟随管理员策略,且不会超过硬上限 100 步、5 分钟、1 MiB 和 4 MiB;会话在执行期间被独占,外部交错动作返回 SESSION_BUSY。challenge、popup/page-crash interrupt、revision mismatch、歧义、超时或任一步错误都会停止后续步骤;dialog/download 是否作为状态中断停止由 stopOn 控制,未显式设置时保持安全默认;敏感输入结果只返回长度。

page_workflow({"sessionId":"ses_...","expectedTabId":"tab_1","steps":[
  {"op":"open","url":"https://test.example.com/profile"},
  {"op":"click","target":{"role":"button","name":"编辑"}},
  {"op":"snapshot","format":"compact","maxBytes":8000}
]})

page_workflow_execute 使用同一套管理员策略和硬上限,但只接受 WorkflowStepSchema:不接受 type 字段,每一步使用 op 和语义目标,scroll.amount 按策略限制(硬上限 1–20),select.values 会串行执行。stopOn 只控制可选的 navigation/dialog/download/ambiguity 停止条件;challenge、popup/page-crash、revision mismatch、超时和错误仍始终停止。遇到中断时返回 stoppedReason 与有界当前 Snapshot。超过当前策略步数返回 WORKFLOW_STEP_LIMIT_EXCEEDED

browser_start 可传 workspaceNameworkspaceRetention。工作区生命周期通过 workspace_list/workspace_get 查询;workspace_handoff 后所有 Agent 写操作返回 USER_CONTROL_HARD_STOP,只有一次性 lease 与 humanConfirmed: trueworkspace_resume 才能恢复。启用租户认证时,会话/工作区工具必须携带有效 tenantIdtenantToken;服务按租户过滤并拒绝跨租户 session/workspace 访问。retain/keep_until 工作区记录只在当前进程内保留,并受服务端 1 分钟至 7 天 TTL 上限约束,进程重启不会恢复浏览器或记录。

一个会话的受控 Tab 数按管理员策略限制(硬上限 32)。新窗口通过 URL 策略后才进入 page_list_tabspage_switch_tab 切换后续页面操作的目标,Tab 之间的 tabId、page revision、semantic ref 和 Snapshot history 完全隔离;headed handoff/reopen 会恢复已打开 Tab 的数量与 URL;超限弹窗自动关闭并记录 TAB_LIMIT_EXCEEDED

browser_capabilities 返回服务版本、当前策略、工具清单、有效资源限制、并发/Tab/TTL 限制和私网开关,并明确列出禁止的 raw_evaluateraw_selectorraw_cdpunmanaged_extension_loadingarbitrary_extension_path 等底层能力。代理、确定性环境和受管扩展配置通过受校验的高层管理面提供,不开放原始协议或任意路径注入。

安全阻断事件

popup、原生 dialog 和 download 仍默认关闭、dismiss 或 cancel,不会扩大能力边界;但它们不再静默消失。browser_status.interrupts 返回累计数量、最新 sequence 和最多 16 条脱敏事件摘要,类型包括 POPUP_BLOCKEDDIALOG_BLOCKEDDOWNLOAD_BLOCKEDPAGE_CRASHED。摘要不包含 dialog 正文、下载 URL 或页面敏感内容。

page_extract 同时限制条数、单字段字符数和完整 JSON 的 UTF-8 字节数。字段会在页面侧先截断,累计结果超过服务端硬上限时返回 RESOURCE_EXHAUSTED,避免把无界页面文本带入 MCP 或 Redis。

Headless、headed 与人工接管

headless: true 适合 CI 和无图形桌面测试,但不能人工操作。如果检测到 Cloudflare/Turnstile/CAPTCHA 或其他机器人挑战,会话进入暂停状态。此时只允许状态、语义快照、截图、有界等待、停止和人工接管;导航、点击、输入、选择和滚动全部拒绝。

headless: false 使用 headed Firefox。browser_reopen_headed 不是挑战求解器:它只把已暂停会话交给受信任操作员。操作员完成站点要求后,调用方仍必须显式发送 browser_resume({"sessionId":"ses_...","humanConfirmed":true});服务端会再次检测,挑战仍在时保持暂停。没有图形桌面的服务器不能提供本地人工接管,应停止或转到经过审批的人工流程。

普通人工复核可调用 browser_handoff,服务会在需要时以 headed 模式重启同一服务端 profile,并进入 USER_CONTROLLED。此期间页面读取和写入都 hard-stop;返回的 lease token 只出现一次,服务仅保留 SHA-256 摘要。操作员结束后调用 browser_takeover 并提供 token 与 humanConfirmed: true,服务重新扫描 challenge 后才恢复。lease 过期不会自动把控制权交回 Agent。本地 stdio 无法从密码学上证明“确认”一定来自人类,因此宿主/操作台必须把这两个工具置于受信任的人机审批边界,不能让普通 Agent 自行完成整套交接。

测试自己的 Turnstile fixture 时,应使用 Cloudflare 官方测试 sitekey/secret,不要把生产凭据或生产挑战放进自动化测试。参见 Cloudflare 官方 Turnstile 测试密钥文档。测试密钥只用于测试环境;遇到真实生产挑战,本服务的合格行为仍然是暂停,不是“通过”。

集群运行

Master 仍是本地 stdio MCP 进程;集群模式只把任务队列放到 Redis。先启动 Redis 和 Worker:

docker compose -f docker-compose.cluster.yml up --build

这个 compose 文件提供的是单实例 Redis 开发环境;原生 Redis Cluster 通常连接已有的托管/运维集群,不要把该 compose 的单实例地址直接当作 Cluster 启动节点。

启动前必须设置 BROWSER_ALLOWED_HOSTS。本地 MCP 配置还需要设置 REDIS_URL,Worker 使用 npm run start:worker 对 Redis 队列进行消费。当前集群 Worker 的 HTTP 模式只支持受策略约束的 GET/HEAD;浏览器模式使用服务端受控 Firefox 会话。调高 WORKER_CONCURRENCY 时应同步调高 BROWSER_MAX_SESSIONS

Worker 与 MCP 进程都读取相同的 BROWSER_SESSION_TTL_MS,浏览器-only Worker 不会 创建控制面 Redis 连接;请在所有 Worker 上保持会话 TTL、并发和 URL 策略配置一致。

适配器支持两种 Redis 形态:

  • 单实例本地开发形态:设置 REDIS_MODE=standaloneREDIS_URL

  • 原生 Redis Cluster:设置 REDIS_MODE=clusterREDIS_CLUSTER_NODES(逗号分隔的启动节点)和相同的 REDIS_SHARD_COUNT。每个“租户 + 分片”使用 Redis hash tag,单次 Lua 出队/租约操作保持在一个 Cluster slot 内;不同租户和分片可以分散到不同节点。REDIS_URL 可用 rediss://user:password@node:port 为 Cluster 节点提供统一 ACL/TLS 配置。

任务、URL 排重、Worker 心跳和状态查询都按租户隔离。同一个 URL 可以被不同租户分别提交;同一租户内仍然是原子排重。URL 声明成功但任务入队失败时会执行租户内补偿释放,避免失败提交占用长期排重 TTL。生产环境设置 TENANT_CREDENTIALS_JSON 后,四个 cluster_* 工具必须携带对应的 tenantId 和 32 字符以上 tenantToken;角色为 read 的租户只能查询,submit 才能提交任务。token 只在 MCP 进程内校验,不写入 Redis 任务记录、审计事件或错误响应。Worker 用 WORKER_TENANTS=tenant-a,tenant-b 声明可信消费范围,未配置时仅消费 default 租户。

cluster_* 是跨进程的控制面能力;browser_* 会话仍归属当前 MCP/Worker 进程,不会因为租户字段而共享浏览器 profile。无 TENANT_CREDENTIALS_JSON 时仅适合本机开发,默认租户为 default;生产部署应同时使用 TLS/ACL、密钥管理和独立的 Worker 租户白名单。 启用 TENANT_CREDENTIALS_JSON 后,browser_*page_*workspace_* 工具也必须携带对应的 tenantIdtenantToken;凭据只在 MCP 进程内校验,绝不转发给 manager、浏览器页面、Redis、审计或错误响应。浏览器工作区不会跨进程共享,租户认证不等于浏览器状态持久化。

错误、审计与测试

工具失败仍返回 MCP isError: true,并在 structuredContent 和短文本中提供稳定 JSON:

{
  "ok": false,
  "sessionId": "ses_...",
  "traceId": "tr_...",
  "error": {
    "code": "TARGET_NOT_FOUND",
    "message": "The target was not found.",
    "retryable": false,
    "details": {}
  }
}

常见 code 包括 SESSION_NOT_FOUNDSESSION_EXPIREDINVALID_STATESESSION_PAUSED_CHALLENGENAVIGATION_DENIEDPRIVATE_NETWORK_DENIEDTARGET_NOT_FOUNDTARGET_AMBIGUOUSACTION_TIMEOUTBROWSER_LAUNCH_FAILEDINTERNAL_ERROR。输入、密码、Cookie、Authorization、页面全文和 URL query 不写入普通审计;审计为服务端追加 JSONL。

npm run typecheck
npm test
npm run build

MCP 契约测试覆盖 tools/list 的精确工具集合、严格 schema、注解、输入错误、截图路径隔离和 manager stub 调用。默认集成测试使用注入 launcher,不启动真实浏览器。安装项目锁定的 Firefox 后,可运行 npm run test:firefox,以本机真实 Firefox 访问本地 fixture;该测试不会访问、求解或统计真实 Cloudflare/CAPTCHA 页面。

只有 npm run test:firefox 在部署宿主通过后,才可把该宿主标记为 Firefox 运行时就绪。单元测试、类型检查或 fake-launcher 集成测试通过,不等价于本机 Firefox 可启动。

故障排查

  • 启动立即失败:检查 BROWSER_ALLOWED_HOSTS 是否设置、是否包含裸 *、URL/端口/空项;检查数据目录和审计路径是否为可写的绝对路径。

  • 浏览器启动失败:运行对应的 npm run install:firefoxnpm run install:chromium,确认 Node、lockfile 与 Playwright 版本一致,再运行真实浏览器测试。服务只启动 Playwright 锁定的内核,并核对实际版本;不会回退到系统 Firefox、Chrome/Edge,也不会跨引擎替代。Windows 上 Firefox 若只看到 spawn UNKNOWN,请同时检查“事件查看器 → Windows 日志 → 应用程序”的 SideBySide 事件;若指向 mozglue 激活上下文/程序集错误,应在受支持的干净 Windows 或 Linux 宿主复验,不要修改或替换浏览器二进制。

  • 导航被拒绝:确认 URL 为 HTTP(S)、主机精确命中 allowlist,且 DNS 结果不是私网/元数据地址。不要用重试绕过策略。

  • 动作暂停:先调用 browser_status/page_screenshot。挑战状态下不要点击、输入、滚动、导航或刷新;请求人工接管或停止。

  • 目标找不到/歧义:先重新 page_snapshot,改用唯一的 role/name、label 或 testId;不要改成 CSS、XPath 或坐标。

  • stdout 出现非 JSON-RPC 文本:将调试日志改写到 stderr,并检查启动脚本、shell profile 和第三方包装器。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI applications to automate your existing browser using your logged-in profile. Provides fast, private browser automation that avoids bot detection by working with your real browser fingerprint.
    5,899 npm
    Apache 2.0
  • A
    license
    A
    quality
    D
    maintenance
    Enables browser automation with anti-detection features, including navigation, interaction, form filling, and session management.
    22
    9
    MIT