Skip to main content
Glama

Daedalus

通过 Chrome 扩展实现远程浏览器控制。评估桥接 + 持久热修复 + 每标签页控制 + 截图、CDP、Cookie、网络捕获,适用于 Chrome 运行扩展的页面。

安装

安装前须知: 普通评估使用无横幅的 MAIN 世界注入。当无源 CSP 探测无法确认动态编译可用时,Daedalus 会尝试 CDP 回退;成功附加会在该回退运行时显示 Chrome 的“Daedalus 正在调试此浏览器”横幅。保持 CDP 会话或网络捕获可能会延长附加时间。该横幅标识调试器附加,而非值完整性保证。

在 Chrome 中加载已解压的扩展(extension/):

  1. 访问 chrome://extensions

  2. 启用开发者模式

  3. 点击加载已解压的扩展程序,选择 extension/

首次安装时会自动生成唯一令牌,并存储在 chrome.storage.local 中。可通过扩展选项页面(拼图图标 → Daedalus → 选项)查看/更改。

对于多标签页并行抓取,请禁用 Chrome 的后台标签页节流:

chrome --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding

桥接本身(server.py)仅依赖标准库,无需安装。CLI 和进程内 MCP 端点是本仓库中的 Python 包:

pip install .            # the `daedalus` CLI
pip install ".[mcp]"     # ... and the MCP endpoint's dependencies

如果没有 mcp 额外依赖,桥接仍会启动并提供所有其他功能;它会在启动时报告 MCP 引导失败,且 /mcp 不会被提供。

Related MCP server: Real Browser MCP

工作原理

  1. 令牌:安装时通过 crypto.randomUUID() 生成一次,存储在 chrome.storage.local 中

  2. 标签页 ID:Chrome 原生 tabs API——每个标签页由其 Chrome tabId 标识,在创建/更新时注册,并带有 30 秒的 chrome.alarms 心跳

  3. 单一 SSE 流:background.js 打开一个持久的 fetch SSE 连接(tab=extension),并将传入命令分派到正确的标签页

  4. 页面桥接:content.js(ISOLATED 世界)在后台和 page.js(MAIN 世界)之间中继 window.GM 消息。评估首先使用 chrome.scripting MAIN 世界注入。无源动态编译探测将 CSP 阻止的页面路由到 CDP;在那里附加失败则到达页面中继。每个通道都以页面 MAIN 世界语义执行。

  5. 热修复:存储在扩展范围的 chrome.storage.local 键 daedalus-hotfixes 中(非按令牌),由后台在每个符合条件的顶层页面加载时重放——MAIN 世界注入,或页面 CSP 禁止动态编译时使用 CDP——并且对于非永久修复进行版本门控。轮换令牌既不会隔离也不会清除此存储。

发送命令

Daedalus 将其扩展命令面作为 MCP 服务器暴露在 <your-bridge>/mcp(流式 HTTP 传输)。在分派请求之前,它要求 Bearer 值必须与通过 CLI 现有配置路径解析的桥接令牌完全匹配:TOKEN 覆盖 DAEDALUS_TOKEN,包括可选的 _settings 提供程序。如果没有配置令牌,MCP 面将以 401 失败关闭。该端点仅在安装了 mcp 额外依赖时存在(见上文“安装”)。将其添加到 Claude Code(或任何 MCP 客户端):

{
  "mcpServers": {
    "daedalus": {
      "url": "https://daedalus.example.com/mcp",
      "headers": { "Authorization": "Bearer <your-bridge-token>" }
    }
  }
}

桥接令牌是扩展在安装时生成的令牌——可在扩展选项页面(拼图图标 → Daedalus → 选项)中查看。

该示例使用公共主机名作为 MCP 服务器的前端,但传输的默认允许主机仅限回环(127.0.0.1:*,localhost:*):在 DAEDALUS_MCP_ALLOWED_HOSTS 中指定公共主机名,否则代理请求将被拒绝。有关所有三个 MCP 设置,请参阅下文“服务器”。

7 组共 40 个工具——标签页、评估/调试、媒体、Cookie、CSS/阻止、热修复、网络/CDP。有关完整列表,请参阅 CLAUDE.md 的 <mcp> 部分,或调用 MCP 端点上的 tools/list。

手动验证辅助工具(无需 MCP 客户端):

TOKEN=<tok> python3 scripts/mcp_probe.py list
TOKEN=<tok> python3 scripts/mcp_probe.py call title '{"tab_id":"<tabId>"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call screenshot '{"include_image":true}'

CLI

一个 shell CLI 作为本仓库中的 daedalus-cli wheel 提供(daedalus_cli/),安装为 daedalus 命令。它从环境变量读取 DAEDALUS_URL 和 DAEDALUS_TOKEN,TOKEN 作为一次性覆盖,ID=<tabId> 用于按标签页定位(省略则广播):

DAEDALUS_TOKEN=<tok> daedalus tabs
DAEDALUS_TOKEN=<tok> ID=<tabId> daedalus title
DAEDALUS_TOKEN=<tok> daedalus exec myid 'document.title'

daedalus --help 列出每个子命令,每个子命令都有自己的 --help。您发送的 exec 代码是表达式或函数体,其返回值作为结果返回——有关约定,请参阅上文“发送命令”。

还提供了一个可选的导入接缝:如果 sys.path 上可导入名为 _settings 的模块,CLI 将使用其 setting(name, default) 和 required(name) 函数来获取 DAEDALUS_URL 和 DAEDALUS_TOKEN,而不是内置的环境变量回退。TOKEN 环境变量仍然是一次性令牌覆盖并优先;没有 _settings 时,DAEDALUS_URL 使用其默认值,DAEDALUS_TOKEN 是必需的。ID 仍然是用于按标签页定位的环境变量。

或者原子地发布原始命令文件(无 MCP,无 CLI)。不支持直接重定向到最终的 .json 名称,因为流可能会在写入器完成之前观察到文件。写入一个以 .tmp 结尾的兄弟名称,然后在同一目录中重命名:

# Broadcast to all tabs
commands_dir="$DAEDALUS_DIR/commands"
final="$commands_dir/<token>.json"
tmp="$(mktemp "$commands_dir/.<token>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
  mv "$tmp" "$final"

# Target a specific tab
final="$commands_dir/<token>_<tabId>.json"
tmp="$(mktemp "$commands_dir/.<token>_<tabId>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
  mv "$tmp" "$final"

读取器忽略兄弟 .tmp 名称。如果较旧的写入器在最终名称处暴露了格式错误的 JSON,读取器会保持其不变并重试,而不是删除可能正在进行的写入。原子重命名后,SSE 流会传递并消费该命令。结果位于 $DAEDALUS_DIR/results/<token>_<tabId>.json(按标签页)和 $DAEDALUS_DIR/results/<token>.json(最后写入者胜出)。page-main 注入和页面中继评估完成可以携带 exec_ms 字段——页面上下文执行时间(毫秒)。页面可以在任一通道上伪造或省略此页面计时字段。CDP 完成不携带 exec_ms。与排队传递关联的结果还携带 roundtrip_ms——服务器观察到的完整往返时间(毫秒),从命令入队(PUT /command)到结果到达(POST /result);它涵盖队列等待 + SSE 传递 + 客户端中继 + 执行 + 返回行程,因此当两个字段都存在时,roundtrip_ms − exec_ms 近似于传输/队列开销。这些测量使用不同的时钟:exec_ms 使用页面的 performance.now(),而 roundtrip_ms 使用服务器墙钟毫秒,因此它们的差异是近似值,而非精确减法。没有 _did 的旧框架没有 roundtrip_ms。

异步支持

默认的 page-main 通道通过页面拥有的 eval 运行表达式,通过页面拥有的 Function 运行函数体;异步包装器提供顶层 await。在提交源代码之前,后台仅注入一个常量 Function 探测。如果该探测返回 true,则尝试一次源代码注入,每个结果都是终态的:值、异常或注入传输错误都会报告,而不会在 CDP 上重试源代码。页面拥有 Function 并可以影响此路由提示,但探测不包含提交的源代码,因此选择另一个通道不会复制其副作用。

如果无源探测未返回 true(最常见的原因是页面 CSP 阻止动态编译),Daedalus 会尝试 CDP。成功附加会显示 Chrome 的调试器横幅。Runtime.evaluate 使用 REPL 模式进行顶层 await;包含 return 的源代码被视为函数体,除非包装器探测将其解析为表达式。该包装器探测是解析器启发式,而非非执行安全边界:操作者精心构造的源代码可以逃逸并运行。在最终 CDP 评估分派后,每个结果都是终态的。只有在提交的源代码运行之前发生附加或形状失败才会到达页面中继。CDP promise 结算限制为 10 秒,即使保持会话或捕获保留了调试器附加,结果和异常对象句柄也会被释放。

在您不控制的页面中评估的 JavaScript 返回的值由该页面选择,无论哪个通道执行了它。world 字段仅记录哪个执行通道运行了提交的源代码;它是用于 CSP 和调试器行为的诊断元数据,而非信任信号。其评估值为 page-main(普通注入)、cdp(检查器回退)和 page:<hostname>(最终中继),其中 <hostname> 是内容脚本的 location.hostname。后台添加 page: 前缀,因此中继主机名不能与 cdp 或 page-main 冲突;此命名空间事实不说明返回值的任何内容。CLI 将该字段渲染为 channel=...,仪表板也这样做,MCP exec、put、result 和 ping 保留确切的 world 值。没有分配信任类别。

默认注入和 CDP 回退保留了文档化的宽松模式案例的经典脚本行为:with 编译,旧式八进制字面量被接受,未声明的赋值创建全局变量。CDP REPL 模式还允许重复的 let 或 const 声明。CLI 和 MCP 结果等待默认为 15 秒;调用方超时不会取消页面中已运行的代码。Blob 中继分别等待最多 10 秒(包含 await 的代码)和 3 秒(其他情况),然后报告回退超时。

中继关联保持描述性和有界性:发送方标签页必须匹配,一个接受的消息消费随机中继 ID,并且在无源注入探测和预分派 CDP 回退完成之前不会注册中继 ID。这些控制防止跨标签页或重复中继完成;它们不建立值完整性。

最多可有 1,000 个页面中继条目处于活动状态。容量满时的新回退会收到一个终态容量错误,而不会驱逐活动工作。每个注册条目在 300,000 毫秒后过期,如果没有同标签页完成或更早的发送失败将其移除,则会收到一个终态超时错误。

仪表板

位于 <your-bridge>/dashboard 的基于浏览器的控制面,无需 CLI 即可驱动整个扩展命令集——实时标签页列表、评估 REPL、截图、Cookie、热修复、阻止规则、网络捕获、CDP、CSS 注入、fetch 计时、上传浏览器。

  1. 在扩展控制的标签页中打开 URL。

  2. 滚动到 §12 设置,粘贴扩展选项页面(拼图图标 → Daedalus → 选项)中的令牌。保存。

  3. 当仪表板订阅了实时事件时,顶栏中的 SSE 状态点变为青色。

由 server.py 直接从仓库的 dashboard/ 目录提供(原生 JS + ES 模块,无需构建)。实时更新通过现有的 /stream 端点:当 /register 更新现有标签页时,以及 /sync-tabs、/unregister 和 /result 的成功路径上,server.py 将事件入队到 commands/<token>_dashboard/<ts>_<uuid>.json;/unregister 即使没有标签页存在也会发出。仪表板将其作为 kind:'event' 帧消耗。

注意: 扩展的内容 + 页面脚本会注入到匹配的页面,包括仪表板的标签页。广播评估命令(exec -b)也会在仪表板内运行——优先使用按标签页定位,或在广播破坏性代码前关闭仪表板。

自修补

热修复

持久化小补丁,在每个符合条件的顶层页面加载时重放。通过 MCP 工具:store_hotfix、list_hotfixes、clear_hotfix、clear_hotfixes、set_permanent。示例(通过探测脚本):

TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"my-fix","code":"console.log(\"patched\")"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"always-on","code":"console.log(\"baseline\")","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call set_permanent '{"fix_id":"my-fix","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call list_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfix '{"fix_id":"my-fix"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes '{"include_permanent":true}'

热修复默认按版本门控。扩展版本更改后,保留的非永久修复会被跳过而非删除;存储修复会将记录更新到当前版本,并使其保留的非永久修复再次符合条件。将修复标记为永久(通过 store_hotfix 上的 permanent: true,或 set_permanent)以在版本更改时重放。clear_hotfixes 默认移除非永久修复并保留永久修复;传递 include_permanent: true 以移除整个存储。

扩展命令

后台服务工作线程接受类型化命令(通过 MCP 工具发出,或向 $DAEDALUS_DIR/commands/ 写入带有 "type": "..." 的 JSON):

命令

用途

screenshot

将可见标签页捕获为 PNG

cdp

发出原始 Chrome DevTools Protocol 调用

net-capture / net-capture-stop / net-capture-get

通过 CDP 进行完整的请求/响应拦截

cookies / set-cookie / remove-cookie / clear-cookies

Cookie 容器访问

open-tab / open-tabs / focus-tab / close-tab / navigate / reload

标签页控制

inject-css / remove-css

按标签页注入 CSS

block-requests / unblock-requests / list-block-rules

declarativeNetRequest 拦截

store-hotfix / clear-hotfix / clear-all-hotfixes / list-hotfixes / set-permanent

热修复管理(永久修复在版本升级后仍然保留)

ext-reload

从磁盘重新加载扩展本身

fetch-timings

用于 fetch 中继的诊断环形缓冲区

GM 桥接

window.GM(位于 page.js 中,MAIN 世界)提供以下 Tampermonkey 风格子集:

方法

描述

GM.getValue(key, default)

从扩展级 chrome.storage.local 读取非保留字符串键(不按 token 区分)

GM.setValue(key, value)

向扩展级存储写入非保留字符串键

GM.deleteValue(key)

从扩展级存储删除非保留字符串键

GM.listValues()

列出非保留存储键

GM.xmlhttpRequest(opts)

后台中继的 HTTP 请求(不受 CSP 限制)

GM.addStyle(css)

注入 CSS

GM.setClipboard(text, type)

写入剪贴板

GM.notification(opts)

桌面通知

GM.openInTab(url, opts)

打开新标签页

GM.download(opts)

触发下载

GM.info

脚本元数据

Cookie 访问是操作员能力,而非页面能力:它通过上述 token 认证的 cookies / set-cookie / remove-cookie / clear-cookies 命令进行,并且有意不暴露给页面上下文——page.js 在每个匹配的顶层页面中运行,否则这些页面可以读取其自身 document.cookie 无法看到的 cookie。

架构

Browser (matching tab)                           Server (your bridge host)
┌────────────────────────────────────────────┐   ┌──────────────────────┐
│ MAIN world                                 │   │ bridge (server.py)   │
│  ├─ page-main: default injection channel   │   │ /stream?token        │
│  └─ page.js: GM + relay channel            │   │ watches commands/    │
│            ▲                               │   │                      │
│            │ window.postMessage            │   │ /result writes       │
│ content.js (ISOLATED)                      │   │    results/          │
│            ▲                               │   │                      │
│            │ chrome.runtime                │   │                      │
│ background.js (service worker)             │   │                      │
│  ├─ CDP: CSP fallback channel              │   │                      │
│  ├─ single SSE stream ◄────────────────────┼───┤                      │
│  └─ POST result, fetch ────────────────────┼──►│                      │
└────────────────────────────────────────────┘   └──────────────────────┘
  • 单条 SSE 流:后台打开一条 fetch SSE 连接(tab=extension),并通过 chrome.tabs.sendMessage 将命令路由到目标标签页。30 秒看门狗在流过期时强制重连。

  • 按标签页路由:命令针对特定的 Chrome tabId,或广播到所有标签页。

  • CSP 行为:GM.xmlhttpRequest 将 HTTP 工作发送到后台服务工作线程的 fetch。Eval 通常使用无横幅的 MAIN 世界注入。当无源探测报告动态编译不可用时,CDP 提供 CSP 回退,并在附加时显示 Chrome 的调试器横幅;附加失败则到达页面/Blob 中继。得到的 world 值描述该通道,不做出任何完整性声明。

服务器

使用三个必需设置启动桥接——没有 DAEDALUS_DIR 或 DAEDALUS_PORT 时它在启动时退出,并且没有配置 token 时每个桥接控制路由都默认失败关闭:

DAEDALUS_DIR=<data-dir> DAEDALUS_PORT=<port> DAEDALUS_TOKEN=<bridge-token> python3 server.py

server.py 在你使用的任何监管程序下运行(此处为 systemd 单元)。如果将其暴露到 localhost 之外,请在它前面放置一个 TLS 终止反向代理;桥接本身使用纯 HTTP。桥接控制和存储路由将其 token 与一个配置的密钥进行比较,通过 CLI 配置路径解析:TOKEN 是一次性覆盖,否则需要 DAEDALUS_TOKEN(嵌入的 _settings 模块可以提供它)。配置缺失和不匹配均默认失败关闭。只有面向页面的 POST /segment 和 GET /segment-status 路由改用作业范围的 capability。

进程内 MCP 前端接受三个可选设置,因描述同一个监听器而一起记录:DAEDALUS_MCP_PORT(默认 8086)是它绑定的回环端口;其工具处理器使用桥接实际绑定的回环 URL,包括当 DAEDALUS_PORT=0 时;DAEDALUS_LOCAL_URL 显式覆盖该 URL,用于独立 MCP 部署前置运行在其他位置的桥接;DAEDALUS_MCP_ALLOWED_HOSTS(默认 127.0.0.1:*,localhost:*)是其 DNS 重绑定保护接受的逗号分隔主机白名单,因此用公共主机名前置 /mcp 意味着在该处命名该主机名。

端点:GET /stream、GET /tabs、GET /health、GET /dashboard[/<asset>]、POST /register、POST /sync-tabs、POST /unregister、POST /poll、POST /result、PUT /command、GET /result、POST/GET/DELETE /upload、GET /screenshot、POST /segment-job、POST /segment + GET /segment-status。POST /segment-job 需要配置的桥接 token;只有 POST /segment 和 GET /segment-status 使用作业范围的 sig。有关负载和端点说明,请参阅 CLAUDE.md。

POST /poll 在存在旧版广播命令文件时消费并删除它。

PUT /command 入队到按目标划分的 FIFO 目录队列(发往同一标签页的连续命令不再互相覆盖),为每个命令盖上投递 id(_did)以便扩展对重投递的帧去重,并在 DAEDALUS_CMD_TTL 秒(默认 90)后 TTL 丢弃未被认领的命令。后台收集器应用该 TTL,并即使从未有 SSE 消费者连接也删除空队列目录。GET /health 报告流/注册表/最后投递的活性,用于检测静默死亡的桥接。

桥接拒绝重复的权威载体,而不是选择其中一个值: 这包括查询字符串或 JSON 主体中的 token,以及分段 capability 路由上的 job / sig,即使重复的值相等或为空。 MCP 传输同样拒绝重复的 Authorization、Mcp-Session-Id、 Host 和 Origin 头,以及分段工具的重复 job 参数。

当扩展发布结果时,服务器将命令的 _did 暴露为 deliveryId,并分配新的 resultGeneration。CLI 和 MCP 等待者首先 窥视共享结果槽,同时匹配命令 id 和 deliveryId,然后 使用 GET /result?...&consume=1&expected=<resultGeneration> 有条件地消费该代。 如果另一个结果在这两个请求之间替换了该槽,则有条件消费将 新结果保留在原位,并报告其未被消费。没有 expected 的裸 consume=1 仍然是对当前槽的破坏性兼容读取。

GET /stream 无限期保持 SSE 连接打开,每 DAEDALUS_STREAM_KEEPALIVE 秒(默认 15)用 keepalive 注释证明活性,而不是按定时器循环连接;DAEDALUS_STREAM_MAX_AGE(默认 3600)仅是最后手段的上限。关闭路径由 tests/test_stream_lifecycle.py 固定。

DAEDALUS_MAX_BODY_SIZE(默认 64 * 1024 * 1024 字节,64 MiB)限制 POST、PUT 和 DELETE 处理器读取的请求体;声明的主体大于限制则收到 413,未声明 Content-Length 的请求收到 411,而不是被当作空主体读取。在中继更大的分段或其他负载时增加它。

基于文件系统的调用者值使用一个路径组件策略:它拒绝 ..、C0/C1 控制字符和代理字符、Windows 无效路径字符 和设备名、尾随点或空格,以及超过 240 字节的 UTF-8 编码。 桥接 token 更严格,也拒绝点和下划线。其他 UTF-8 作业名称被接受,因此客户端必须在查询字符串中对它们进行 URL 编码。

POST /segment-job 将三个固定配额复制到每个新作业记录中: DAEDALUS_MAX_SEGMENT_INDEX(默认 99999)、 DAEDALUS_MAX_SEGMENTS_PER_JOB(默认 10000)和 DAEDALUS_MAX_SEGMENT_JOB_SIZE(默认 4 * 1024 * 1024 * 1024 字节, 4 GiB)。更改这些设置会影响随后创建的作业;具有 存储配额的作业继续使用其记录的值。DAEDALUS_MAX_BODY_SIZE 也限制每个单独的分段请求。

安全

安装扩展前请阅读此部分。

它将 window.GM 垫片注入每个匹配的顶层页面 (<all_urls>,MAIN 世界), 这正是让随 put 发送的脚本能够发出跨源请求的原因。其 后果是需要慎重考虑的部分:你访问的任何匹配站点 都可以调用该垫片,因此该站点可以通过扩展的权限发出跨源请求, 就像具有通配符 @match 的用户脚本管理器一样。如果这对你的浏览 不可接受,请将 extension/manifest.json 中的 matches 缩小到你实际驱动的宿主,并接受桥接随后在其他页面上不做任何事。

桥接 token 和服务器 URL 无法通过页面的 GM 存储方法访问。 它们位于 chrome.storage.local 中 daedalus- 前缀下, 中继拒绝为页面脚本读取、写入或枚举该命名空间。没有 该规则,任何访问的站点都可以用 GM.getValue('daedalus-token') 读取你的桥接 token, 或用 GM.setValue('daedalus-server', ...) 静默地重新指向桥接。tests/test_repo_contract.py 固定了该规则。

热修复源是刻意可被页面观察的状态,而非机密扩展状态。 后台选择永久修复加上与扩展版本匹配的非永久修复, 并通过普通 eval 使用的相同通道在页面中运行每一个——在页面 允许动态编译时进行 MAIN 世界注入,不允许时使用 CDP——因此页面 可以在任一通道上观察其执行的源。

MCP 持有者不能为 MCP 进程指定要读取的服务器主机文件。 MCP 的 put、CSS 注入/移除和热修复存储工具仅接受内联源。 本地 CLI 保留其文件路径便利性,但 CLI 进程 读取操作员选择的文件并以内联方式提交其内容。因此持有 桥接 token 授予文档化的浏览器和扩展控制权限,包括读取存储的 热修复源,但不会通过这些工具增加任意的宿主文件系统读取。

桥接控制与存储路由要求配置桥接令牌;/segment 与 /segment-status 是唯一的能力例外。 服务器将请求令牌与从 TOKEN 或 DAEDALUS_TOKEN 解析出的密钥进行比较,并在未配置密钥时拒绝请求。POST /segment-job 需要该桥接令牌,因为它会铸造作业作用域的能力;不受信任的页面 JavaScript 仅使用该能力来提交和查询该作业。任何持有桥接令牌的人都可以驱动你的浏览器。除非有终止 TLS 的反向代理,否则不要将桥接端口暴露在回环之外,并将令牌视为凭据。

Eval 结果不提供值完整性保证。 在你无法控制的页面中执行的 JavaScript 会返回该页面可以任意选择的值,无论由哪个通道执行。world 字段说明提交的源码是如何运行的,而非是否信任其值:page-main 是普通的 MAIN 世界注入,cdp 是检查器 CSP 回退,page:<hostname> 是中继。强制性的 page: 前缀是在页面上下文之外添加的,因此任何主机名都无法产生 cdp 或 page-main;这种防碰撞特性仅具有描述性。在 page-main 上,页面拥有的 eval 和 Function 绑定既可以读取提交的源码,也可以影响其返回值。

CDP 编译避免了解析页面的 eval 和 Function 绑定,实现会在序列化之前通过引用检索直接句柄。这些传输机制不会改变信任边界:提交的源码仍然读取页面控制的状态,并且可以在 CDP 接收之前通过页面 promise 机制路由任何值(包括原始值)。每当提交的源码使用这些页面控制的路径时,页面也可以在 cdp 上选择返回的值。

中继仅接受与该随机 id 关联的已存储调用,要求发送方的标签页匹配,并且每次只消费一次该条目。降级不会泄露桥接令牌、服务器 URL、投递 id 或结果路由,不会授予额外的扩展或浏览器权限,也不会影响另一个标签页中的调用。这些属性保护路由和浏览器权限;它们不会将中继标记或其返回的 JavaScript 值转变为信任信号。

部署

桥接在回环上使用纯 HTTP。桥接控制与存储路由要求配置的令牌,并在令牌缺失时默认拒绝;只有 /segment 和 /segment-status 使用作业作用域的能力。它刻意不做两件事,因为它们属于其前面的任何组件:

  • TLS 与 CORS。 server.py 不发送任何 CORS 头。当桥接是跨源时,示例 HLS 中继的页面侧 fetch 调用需要同时访问 GET /segment-status 和 POST /segment;代理必须允许页面源访问这两个路由,并处理 POST 预检所需的方法/头。如果状态 GET 被阻止、不可用、非 2xx 或无效,示例会将作业视为全新并重新 POST 每个分段。这些写入仍然替换相同的按索引文件,但运行会失去其恢复/跳过节省。如果部署无法提供该 CORS 策略,请改为通过扩展的 GM 桥接路由这两个请求。

  • 提供存储的上传内容。 仪表板在 /uploads/<path> 链接下载,但桥接没有这样的路由。不要在仪表板源上直接提供调用者提供的上传名称和字节,否则可执行内容将与仪表板携带令牌的存储共享同一源。要么让这些链接不可用,要么让 /uploads/ 重定向到单独的仅下载源,该源强制 Content-Disposition: attachment,使用 application/octet-stream,并发送 X-Content-Type-Options: nosniff。GET /upload(列表)和 GET /screenshot 由桥接本身提供,不需要代理帮助。

使用配置的令牌直接运行它,以上两者就都不存在——其他一切正常。

文件

文件

描述

extension/manifest.json

MV3 清单

extension/background.js

Service worker — SSE、命令分发、fetch 中继、截图、CDP、Cookie、下载

extension/content.js

页面与后台之间的消息中继

extension/page.js

MAIN 世界桥接 — window.GM + eval 处理器

extension/options.html / options.js

令牌/服务器设置 UI

server.py

调试服务器(还在 127.0.0.1:8086 上托管 MCP 守护线程)

mcp_server.py

MCP 服务器 — 通过 HTTP 将扩展命令面桥接到 server.py

scripts/mcp_probe.py

用于手动验证的最小 MCP 客户端辅助工具

scripts/check_versions.py

跨每个版本位置的版本一致性检查/提升

.githooks/

pre-commit + pre-push 版本一致性门禁

daedalus_cli/

Shell CLI,作为 daedalus-cli wheel 发布

dashboard/

server.py 在 /dashboard 提供的浏览器控制面

examples/

通过 put 在页面中运行的脚本 — 见下文

tests/、run_tests.py

测试套件;python3 run_tests.py 运行全部测试

示例

examples/ 存放旨在通过 put 在页面内运行的脚本。六个中的五个演示的是桥接的某一部分而非特定站点;Discord 那个刻意针对特定站点,因为回滚虚拟化列表是一种没有真实虚拟化列表就无法展示的技术:

示例

展示内容

request-log-hotfix.js

在 document_start 的永久 MAIN 世界热修复,以及为什么这三个词都具有承重意义

hls-segment-relay.js

/segment + /segment-status 中继:可恢复、幂等、有界重试

kill-hls.js

查找并销毁一个不会停止重试的播放器实例

react-fill-input.js

填充 React 控制的输入框,使 React 自身的状态真正改变

scrape-discord-messages.js

回滚虚拟化消息列表并提取内容

open-tabs.js

最小可能的 GM.openInTab 调用

它们在发送前通过 __PLACEHOLDER__ 替换获取配置,因为 put 发送的是脚本而非调用函数,并且没有办法传递参数。

每个都是异步函数的主体,而非独立脚本:桥接会包装你发送的内容。这就是为什么大多数以顶层 return 结尾,其中一个——scrape-discord-messages.js——还使用了顶层 await;两者在它们实际运行的地方都是合法的。node --check 在 CommonJS 包装器中解析每个文件,这允许顶层 return,但拒绝顶层 await——所以恰好那个文件无法通过语法检查。

开发

python3 run_tests.py 运行每个套件并打印一行汇总,说明它验证了什么:运行了多少个套件、通过了多少测试、跳过了多少。一个所有测试都跳过的套件没有验证任何内容,因此报告为 OVERALL: INCOMPLETE 并返回非零退出码,而不是通过;通常原因是缺少依赖项,其上方的 NO COVERAGE 行会指明套件。安装 mcp 额外依赖(见"安装")是 MCP 套件所需要的。

版本字符串存在于扩展、仪表板和 CLI 包中的多个位置。python3 scripts/check_versions.py 就是清单——它列出每个位置并报告找到了多少个,这就是本段不重复一个会在下次添加时过时的计数的原因。一起提升它们,绝不要手动:

python3 scripts/check_versions.py --set 0.18.0   # rewrite every site
python3 scripts/check_versions.py                # verify the working tree

.githooks/pre-commit 检查索引,.githooks/pre-push 检查每个推送的提交,因此半提升不可能落地。每个克隆都必须选择一次——否则 git 不会运行跟踪目录中的钩子:

git config core.hooksPath .githooks

如果浏览器从与你编辑位置不同的检出加载扩展,请也在那里安装钩子——并记住重新加载扩展会重新读取浏览器侧的文件,而不是你侧的文件。

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MCP clients to drive a real, logged-in Chrome browser for web automation tasks like navigation, clicking, typing, and screenshotting.
    2 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables browser automation over MCP using a real Chrome browser with existing profile, supporting real tabs, downloads, cookies, and RPA workflows.
    104 npm
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP clients to control real Chrome tabs in existing profiles, supporting navigation, interaction, screenshots, and console/network inspection across multiple Chrome instances.
    -
  • A
    license
    B
    quality
    C
    maintenance
    Enables MCP clients to drive a real Chromium browser for automation, including navigation, JavaScript execution, CDP commands, network capture, and multi-tab control.
    7
    GPL 3.0