Skip to main content
Glama
HabaAndrei

custom-chrome-dev-mcp

by HabaAndrei

自定义 Chrome Dev MCP

一个仅限本地的 MCP(模型上下文协议)服务器,让 MCP 客户端——Claude Code,或任何其他支持 MCP 的工具——像真人一样驱动你真实的 Chrome 浏览器。无遥测、无第三方服务、无云端:一切都在你的机器上运行,由共享令牌保护。

它提供了 45 个工具,涵盖导航、标签页、感知、交互、可信输入、可观测性和捕获。


来源

本项目受 Chrome 官方浏览器 MCP 启发——即 Chrome DevTools 团队发布的 Chrome DevTools MCP 服务器,它首次论证了 AI 代理应通过 DevTools 协议而非抓取的 HTML 来驱动浏览器。

我们是在复刻和模仿那个想法,而不是发布它。 我们借鉴了:

  • 前提——将浏览器作为一组 MCP 工具暴露给代理。

  • 无障碍优先的感知——给模型一个紧凑的无障碍大纲,带有稳定的元素引用,而不是一堆原始 HTML。

  • 以 Chrome DevTools 协议作为输入层——真实可信的事件,而不是页面能识别并忽略的合成事件。

本项目刻意偏离的地方:

Chrome DevTools MCP

自定义 Chrome Dev MCP

浏览器

默认启动自己的 Chrome,使用专用的用户数据目录;也可以通过 --browser-url 附加到正在运行的实例

驱动你已经打开的 Chrome

附加方式

通过 DevTools 协议端点连接到浏览器

浏览器内部的 Chrome 扩展,指向你选择的任何标签页

主要目标

调试、检查和剖析页面

像人一样使用该页面

最后一行是这个仓库的全部意义。Chrome DevTools MCP 是一个恰好能驱动浏览器的调试工具;这是一个模仿人的工具,恰好对调试有用。

⚠️ 与 Google 或 Chrome 团队无关联、未经其认可或支持。 这是一个独立的重新实现,旨在学习和模仿他们的设计。如果你想要受支持的东西,请使用官方服务器。


Related MCP server: monkeysee

它刻意保留人类的风格

大多数浏览器自动化都很容易被检测到:isTrusted=false 的合成事件、从未真正移动的焦点、一次性出现在字段中的文本、没有历史记录的干净自动化配置文件。每一个都是信号。

本项目试图消除这些信号:

  • 你真实的配置文件。 操作在你已经在用的 Chrome 中运行——你的 cookie、登录状态、扩展和历史记录。没有任何东西会被指纹识别为“全新自动化”。

  • 可信输入。 realClickrealTypepresshoverdrag 通过 DevTools 协议分发,因此页面收到 isTrusted=true 的事件——与物理鼠标和键盘产生的标志相同。

  • 真正的焦点。 点击聚焦字段确实按顺序移动焦点,而不是在页面背后赋值 .value

  • 真实的按键。 press 发出正确的 rawKeyDown / char / keyUp 序列,带有正确的键码和修饰键,而不是单个合成的 input 事件。

  • 回读验证。 fill 确认字段确实包含文本,因此代理能注意到页面静默拒绝输入的情况——就像人一样。

目标:页面对于代理的行为,应与对坐在键盘前的人的行为完全一致。

快速的合成工具(clicktype)仍然存在——它们更快,在大多数网站上都能用。当页面忽略它们时,请使用可信的等价工具。


工作原理

一种传输方式。MCP 客户端通过 stdio 与服务器通信;服务器通过一个由小型长驻 hub 进程拥有的本地 WebSocket 中继到 Chrome 扩展。

MCP client 1 (Claude) <-stdio-> bin/custom-chrome-dev-mcp.js ─┐
MCP client 2 (Claude) <-stdio-> bin/custom-chrome-dev-mcp.js ─┼─ src/hub.js (127.0.0.1:9876)
MCP client N (Claude) <-stdio-> bin/custom-chrome-dev-mcp.js ─┘              │
                                                                            │ WebSocket
                                                                            ▼
                                                              Chrome extension -> active tab

为什么需要单独的 hub 进程。 只有一个进程能拥有端口 9876,但你可能同时打开多个 Claude 会话,它们都可能需要浏览器。因此,套接字位于 src/hub.js 中,而不是任何单个会话中。每个会话以 role:"mcp" 连接到 hub,扩展以 role:"extension" 连接,hub 在它们之间进行多路复用。第一个启动的会话会分离地生成 hub,因此它比该会话存活得更久;后续会话会发现它已经在监听。

扩展内部有三层:

  1. Walkerpage/walker.js)——注入到页面的 ISOLATED 世界。负责元素解析、稳定的 eN 引用映射和快速的合成 DOM 操作。

  2. CDPcdp/)——用于可信输入、页面上下文 evaluate、整页截图以及控制台/网络缓冲区的 chrome.debugger

  3. Recordingrecording/)——CDP 屏幕录制帧,由离屏文档中的 MediaRecorder 编码为 .webm

🔒 扩展使用共享令牌(AUTH_TOKEN,在 src/config.jsextension/src/config.js 中相同)向 hub 进行身份验证。hub 会丢弃任何提供不同值的对等方。


前提条件

要求

检查

Node.js

18 或更高(在 22 上开发)

node --version

Chrome

Google Chrome 或 Chromium,任何较新版本

chrome://version

MCP 客户端

Claude Code,或任何其他通过 stdio 支持 MCP 的工具

claude --version

无需全局安装、无需构建步骤、无需注册服务。两个运行时依赖(@modelcontextprotocol/sdkws),一切都在 127.0.0.1 上。


本地设置

四个步骤,然后进行验证。预算五分钟。

1. 克隆并安装

git clone <your-fork-url> custom-chrome-dev-mcp
cd custom-chrome-dev-mcp
npm install

在将任何东西连接到 Chrome 之前,先确认树是健康的——离线通道不需要浏览器,不到一秒钟就能完成:

npm test

你应该看到 22 passed。如果失败,请先修复再继续;下游任何东西都不会工作。

2. 将扩展加载到 Chrome 中

  1. 打开 chrome://extensions

  2. 打开开发者模式(右上角开关)。

  3. 点击加载已解压的扩展程序,选择 extension/ 文件夹——是文件夹本身,而不是里面的 manifest.json

  4. Custom-chrome-dev-mcp 出现在列表中。

⚠️ 将其加载到你实际浏览的 Chrome 配置文件中。 Chrome 按配置文件保留扩展,因此加载到“Profile 4”的扩展对在“Default”下运行的窗口是不可见的。如果之后工具报告没有标签页,或者 hub 从未记录 extension connected,这是首先要检查的。chrome://version 显示活动的配置文件路径

扩展 ID 由 extension/manifest.json 中的公共 key 固定,因此它在每台机器上都是相同的——无需在设置之间复制任何内容。

3. 向你的客户端注册 MCP 服务器

使用 CLI——替换你克隆仓库的绝对路径(在项目根目录运行 pwd 会打印它):

claude mcp add -s user custom-chrome-dev-mcp -- node /ABSOLUTE/PATH/TO/custom-chrome-dev-mcp/bin/custom-chrome-dev-mcp.js
  • -s user所有项目注册;-s local 将其限制为当前项目。

  • 注册 bin/custom-chrome-dev-mcp.js——该文件是入口点。指向 src/server.js 将不起作用。

  • 路径必须是绝对路径。相对路径会根据客户端恰好启动的目录进行解析。

  • claude mcp list 确认——你希望旁边有 ✔ Connected

⚠️ 不要手动编辑 ~/.claude.json 它很大,一个放错位置的逗号就会完全破坏 Claude Code。上面的命令可以安全地编辑它。

mcpServers 下添加服务器:

{
  "mcpServers": {
    "custom-chrome-dev-mcp": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/custom-chrome-dev-mcp/bin/custom-chrome-dev-mcp.js"]
    }
  }
}

4. 重启 MCP 客户端

MCP 客户端在启动时只枚举一次工具——在会话中途注册的服务器在重启前是不可见的。重启 Claude,45 个工具就会出现。

重启时,客户端会启动服务器,如果 127.0.0.1:9876 上没有东西在监听,服务器会生成 src/hub.js

5. 验证链条中的三个环节

堆栈是 客户端 → 服务器 → hub → 扩展 → 标签页。请端到端检查,而不是猜测哪个环节断了。

# The hub is up, and the extension found it:
tail -f "$TMPDIR/custom-chrome-dev-mcp-hub.log"
#   [hub] listening on 127.0.0.1:9876
#   [hub] extension connected      <- this line is the handshake succeeding

# Who owns the port (should be src/hub.js from THIS repo):
lsof -nP -iTCP:9876 -sTCP:LISTEN

然后向你的客户端请求 listTabs。一个包含你打开的标签页的 JSON 数组意味着每个环节都正常。接着使用 screenshot——一个 PNG 会保存到 ~/Downloads 并且 内联返回。

对于扩展自身的控制台:chrome://extensionsCustom-chrome-dev-mcpservice workerInspect。扩展端的错误会出现在那里;它们永远不会到达 MCP 客户端。

6. 在实际使用前更改共享令牌

AUTH_TOKEN 带有默认值,在 src/config.jsextension/src/config.js 中定义相同。它是唯一阻止你机器上另一个进程驱动你已登录浏览器的东西。选择你自己的值,在两个文件中更改它(离线测试会断言它们匹配),然后重新加载扩展。


修改代码后

两半部分的重载方式不同,弄错这一点比项目中任何其他事情都更浪费时间:

你编辑的内容

如何生效

extension/ 下的任何内容

chrome://extensions 中点击扩展上的重新加载 ↻。在你这样做之前,Chrome 会继续运行之前加载的构建。

src/ 下的任何内容

重启 MCP 客户端。服务器进程是长驻的,持有旧的工具模式。

src/hub.js

pkill -f src/hub.js——下一次工具调用会重新生成它。


故障排除

症状

原因

修复

claude mcp list 显示 ✘ Failed to connect

路径错误,或入口点不是 bin/

使用 bin/custom-chrome-dev-mcp.js 的绝对路径重新注册

工具完全从客户端中缺失

会话中途才注册扩展

重启 MCP 客户端

Hub 日志中从未显示 extension connected

扩展未加载、加载到另一个 Chrome 配置文件中,或两个 config.js 中的 AUTH_TOKEN 不一致

检查 chrome://version → 配置文件路径;确认两个 token 一致

工具调用挂起后超时

Service Worker 已失效,或扩展侧发生了异常

打开 Service Worker 控制台;点击 reload ↻

端口 9876 被意外进程占用

另一个本项目克隆中的 Hub 占用了该端口

运行 lsof -nP -iTCP:9876 -sTCP:LISTEN,然后杀掉对应的 PID

“对 extension/ 的修改没有生效”

Chrome 仍在运行旧的构建版本

点击 reload ↻

URL is banlisted

extension/src/config.js 中的 BANLIST 屏蔽了该主机

编辑该列表——它随附占位条目

refusing to act: … does not contain expectUrl

expectUrl 守卫正确地触发了

去掉守卫,或将其指向真实 URL

截图路径被拒绝

写入仅允许在捕获目录内进行

使用位于其内部的文件名或路径

工具定位到了错误的标签页

后台标签页抢占了焦点

使用 useTab 固定工作标签页


配置

两者都是可选的、由 src/config.js 在启动时读取的环境变量。

变量

默认值

作用

CUSTOM_CHROME_DEV_MCP_CAPTURE_DIR

~/Downloads

截图和录制内容唯一可写入的目录。

CUSTOM_CHROME_DEV_MCP_WS_PORT

9876

Hub 端口。也要在 extension/src/config.js 中修改,否则两者互相找不到。

实际使用中还值得修改的是 AUTH_TOKEN,它同样定义在 src/config.jsextension/src/config.js 中。返回一个自己的值——它可以防止 其他本地进程驱动你的浏览器。


可用工具(45)

元素支持通过三种方式定位:selector(CSS)、ref(来自 snapshotA11y 的稳定 eN id), 或 name(可访问名称,例如按钮的标签)。下面提到的“target”即指三者中的任意一个。

通用参数,每个工具都可接受:

  • tabId - 操作指定的标签页,而不是当前活动标签页。

  • frameId(来自 listFrames)- 在某个 frame 内部操作,包括顶层文档无法脚本控制的跨域 iframe。

  • expectUrl - 一个守卫:除非标签页的 URL 包含该子串,否则拒绝操作。

useTab 在整次会议期间固定工作标签页,这样后台标签页(如自动播放的视频、通知弹窗)就不会抢走焦点、误导操作。

导航

工具

参数

描述

navigate

url

将标签页指向 URL(替换当前页面)。

newtab

url

的前台标签页中打开 URL,当前页面保留。

back / forward

-

历史记录后退 / 前进。

reload

hard?

重新加载,可选择绕过缓存。

getUrl / getTitle

-

标签页的 URL / 标题(也适用于内部页面)。

waitForLoad

timeout?

阻塞直到标签页完成加载。

标签页与 frame

工具

Args

描述

listTabs

-

所有窗口中每个打开的标签页(idtitleurlactivepinned)。

activateTab

tabId

聚焦标签页及所在窗口。

closeTab

tabId

按 id 关闭标签页。

useTab

tabId?

固定工作标签页,使之后的所有工具都指向它,而不管操作系统的焦点在哪。省略 tabId 时固定当前标签页。

unpinTab

-

取消固定;工具恢复作用到活动标签页。

listFrames

-

所有 frame(包括跨域),格式为 {frameId, parentFrameId, url, origin}

感知

工具

Args

Description

snapshotA11y

-

可见可交互元素的紧凑无障碍摘要,格式为 rol "labelbody" ref=eN**更多优先: **。导航或重新快照后,引用会失效。

snapshot

-

<body> 的原始 outerHTML,截断为 50k。需要精确标记时使用。

getText

目标

单个元素的 innerText,去除首尾空白。

getAttribute

目标, attr

返回属性,若不存在则回退到实时 DOM 属性(valuecheckedhref)。

queryAll

selector, limit

一次性返回所有匹配项的 text/href/visible/visible。

viewport

-

devicePixelEndResult, CSS 视口, 滚动偏移 - 如何把截图像素转换为 CSS px。

交互——合成、快速

由 walker 派发的不可信事件。速度快,对大多数网站够用。

工具

参数

描述

click

目标

每种鼠标事件 click;也聚焦该元素;回退时用 .click()。返回 {focused}

type

目标, text

通过原生 setter 设置字段的值(支持 <input><textarea>、以及 contenteditable)。返回 {value}

fill

目标, text, verify?

聚焦 + 设置 + 读回。文本未生效则抛错。可靠的文本输入路径 —— 优先于 click-then - **type 之外的选项。

assert

目标, text?, value?

无截图验证文本(子串)和 / 或精确值 → {ok, checks}

scroll

目标?, direction?, amount?

将元素 scroll 到视图,或将窗口 jump。top/bottom 跳到极致。

select

目标, value? / label?

按值或可见标签选择一个 <select> 列表项。

check

目标, checked

设置 checkbox/radio,如未处于目标状态才点击。

submit

目标

requestSubmit() 对表单请求提交——适用于没有可点击按钮的表单。

waitForSelector

目标 text, timeout?

一直轮询直到元素解析成功, 文本出现。

受信任输入与模拟 —— CDP

isTrusted=true 的真实事件。此类会附加 chrome.debugger,它在标签页上显示一个持续的黄色*“调试中”*横幅。

工具

参数

描述

realClick

target / x,ybutton?clickCount?

可信点击,包括右键和双击。

realType

target?text

可信的文本输入;如果提供了 target,则先将焦点移到目标上。

press

keystarget?

可信的按键和按键组合:"Enter""Tab""Meta+c"["ArrowDown","Enter"]

hover

target / x,y

将真实鼠标移到元素上以触发 :hover(从而展开菜单和工具提示)。

drag

fromto

可信的“按下-移动-松开”式拖放。

uploadFile

selectorpaths[]

<input type=file> 上设置文件,绕过操作系统选择器。需使用绝对路径。

setViewport

widthheightdeviceScaleFactor?mobile?userAgent?

模拟视口/设备,用于响应式检查。

handleDialog

accept?promptText?

预置对下一个 alert/confirm/prompt 的应答。必须在触发对话框的操作之前设置。

detach

-

解除调试器的附加并清除横幅。下一次 CDP 调用时重新附加。

可观测性 —— CDP,按标签页缓冲

调试器一旦附加就开始捕获,因此如果你想捕获加载期间的活动,请在第一次 CDP 调用后重新加载页面

工具

参数

描述

getConsole

level?limit?clear?

缓冲的控制台日志、警告、错误和未捕获异常。

listNetworkRequests

urlContains?status?failedOnly?limit?

缓冲的请求信息:方法、URL、状态、类型、耗时。

getNetworkRequest

requestIdincludeBody?

查看单个请求的完整信息;includeBody 还会获取(截断后的)响应体。

evaluate

expression

通过 CDP 在页面真实上下文中运行 JS —— 绕过阻止 eval 的内容脚本 CSP。会等待 Promise。在 chrome:// 页面上不可用。

捕获

保存到捕获目录(默认为 ~/Downloads —— 参见配置)。

工具

参数

描述

screenshot

path?format?tabId?

可见视口的 PNG/JPEG —— 保存到磁盘并内联返回,并附带 {devicePixelRatio, cssViewport},因此模型在单次调用中即可看到。

fullPageScreenshot

path?tabId?

通过 CDP 截取视口之外的整个可滚动页面

record

actionpath?tabId?

对标签页进行 start / stop / status 录制,生成 .webm。完全由 MCP 驱动 —— 不需要点击工具栏,也不需要用户手势。录制的是标签页,而非桌面。

path 是捕获目录内部的文件名或路径。缺少的子目录会自动创建;任何解析到该目录之外的东西都会被拒绝。


第一次真实运行

设置的第 5 步证明接线贯通。这一步证明了有趣的部分 —— 页面看到的是一个真实的人,而不是一段脚本。将你的客户端指向任意页面,然后请求:

  1. snapshotA11y —— 紧凑的概要,带有可供操作目标的 eN 引用。

  2. realClick {ref:"e3"} —— 一次可信点击。标签页上会出现一条黄色 “正在调试” 横幅;这就是 CDP 附加的效果,它放在那里就是让你看到的。

  3. evaluate {expression:"'ok'"} —— 在页面上下文中运行 JS,绕过内容脚本的 CSP。

  4. screenshot —— 在你的捕获目录中生成 PNG,同时内联返回。

  5. record {action:"start"}record {action:"stop", path:"clip.webm"} —— 截取标签页生成 .webm。不需要点击工具栏,也不需要任何用户手势;工具栏图标按设计是惰性的,不会启动任何东西。

  6. detach —— 清除那条横幅。

要想看到可信路径带来的差异,安装一个监听器并对比:

// via evaluate
window.__e = []; document.querySelector("button")
  .addEventListener("click", e => window.__e.push(e.isTrusted));

click 报告 falserealClick 报告 true。这种对比正是这个项目的主旨,浏览器测试通道也直接对它进行断言。


运行测试

整个套件分为两个通道,这种拆分就是要点。

离线通道 —— 无浏览器,在 CI 中运行

npm test        # node test/run.mjs --lane=offline

整个过程远不到一秒,只需要 Node 环境。它通过 SDK 的内存传输在进程内与 src/server.js 进行一次真正的 MCP 握手,因此它断言的是服务器实际发布的外在面:

  • 每个已发布的工具都有扩展处理器,反之亦然 —— 镜像架构带来的隐患

  • 没有任何工具名会被两个处理器组声明(它们靠展开合并,所以重复名会静默丢失)

  • 每个工具都带有真实描述以及通用的 tabId/frameId/expectUrl 作用域

  • 每个工具都至少被一个测试覆盖到 —— 加了工具但没有测试,CI 就会失败,而且不需要浏览器

  • 捕获路径白名单真正拒绝 ..、深层 ..、绝对路径以及符号链接逃逸,针对真实解析器进行验证

  • 禁用列表通过行为来检查 —— 它阻断声称要阻断的内容,又不会误伤普通站点

  • hub 只绑定回环地址,两端的 token 匹配,manifest 没有申请过宽的权限,工具栏图标是惰性的,仓库里没有提交任何 *.pem

浏览器通道 —— 驱动真正的 Chrome

# 1. Disconnect the MCP client (close Claude Code, or disable this server for the run)
# 2. Free port 9876 - the hub is long-lived and outlives the session that spawned it
pkill -f src/hub.js
# 3. Start the suite; it binds 9876 itself and waits for the extension
npm run test:browser
# 4. Reload the extension in chrome://extensions so it connects to the suite

⚠️ 第 1 步不是可选的。 一个已连接的** MCP 客户端一旦发现 socket 不在,就会每约 1.2 秒重启当次 hub,所以它马上重新占上端口 9876,测试套件随后以 EADDRINUSE 崩溃。在客户端仍挂着的时候杀掉 hub 并没用 —— 客户端只会再起一个新的。

该套件会立起一个 stress fixture 服务器以及一个与真实 hub 使用相同线的协议的桥接器,所以一次通过的运行会真正校验实际的消息契约。每个套件镜像一个工具组,而每个测试都从重置后的 fixture 页面开始 —— 不会有测试继承其他测试的变更。

最后它会打印工具覆盖,并且只要 45 个工具中有任何一个没被执行,就失败。

选项

命令

效果

npm test

仅离线 lane —— CI 门槛

npm run test:browser

仅浏览器 lane

npm run test:all

同时跑两者

npm run test:list

列出每个套件和测试而不实际运行

node test/run.mjs --grep=fill

只运行套件或名字匹配的测试

测试结构

test/
├── run.mjs                    # CLI: lanes, filtering, coverage, reporting
├── lib/
│   ├── runner.js              # suite registry, isolation, timeouts
│   ├── assert.js              # assertions with diagnostic messages
│   ├── wait.js                # eventually() - polling, not fixed sleeps
│   ├── mcp-probe.js           # real in-process MCP handshake
│   ├── bridge.js              # stands in for the hub; tracks tool coverage
│   ├── fixture-server.js      # serves the fixture pages
│   └── page.js                # the browser session + per-test reset
├── fixtures/
│   ├── index.html             # the fixture page (a real file, with __reset())
│   └── frame.html             # child frame, for frameId targeting
└── suites/
    ├── 01-contract.suite.js   # offline
    ├── 02-security.suite.js   # offline
    ├── 10-navigation.suite.js
    ├── 20-tabs.suite.js
    ├── 30-perception.suite.js
    ├── 40-interaction.suite.js
    ├── 50-trusted-input.suite.js
    ├── 60-observability.suite.js
    └── 70-capture.suite.js

安全说明

这个扩展可以驱动你已登录的浏览器。请阅读本节。

  • 仅限回环。 hub 绑定 127.0.0.1,所以无法从局域网访问,只能由本机上的进程访问。

  • Token 握手。 对端必须在连接时出示 AUTH_TOKEN,否则 hub 直接丢弃。请修改默认生成的默认值(src/config.jsextension/src/config.js 里有相同的常量)——它是用来阻止其他本地进程驱动你的浏览器。

  • 文件写入被约束在捕获目录内。src/capture/capture-path.js 解析每个请求的路径都会拒绝在它目录之外的东西(包括通过 .. 遍历以及符号链接的子路径)。这个保护比看上去更重要:任意路径写入实际上就等于代码执行。

  • 主机禁用列表。 extension/src/config.js 中的 BANLIST 会阻止导航和脚本访问敏感域名(银行、PayPal、Gmail 上都有签名?)。按你的需要调整。注意: 截图和录制捕获的是渲染后的像素,不会被禁用列表过滤。

  • 调试横幅是一个特性。 CDP 工具附加 chrome.debugger 后,会出现一条常驻的黄色 “正在调试” 栏。这是有东西驱动该标签页的可视信号。detach 可以移除它。

  • evaluate 在页面的真实上下文里运行任意 JS

  • 内部页面不受支持 —— 扩展无法在 chrome://chrome-extension:// 网址上执行处理脚本。

  • 签名密钥不在此仓库中。 扩展 ID 由 extension/manifest.json 中的 公开 key 固定,匹配的私有密钥必须留在版本控制之外(.gitignore 会屏蔽 *.pem)。只有在重新打包相同 ID 的 .crx 时才会用到它 —— 以未打包方式加载并不需要。


架构

服务器和扩展是互为镜像的。src/tools/ 中的各工具组,都在 extension/src/handlers/ 里有一个同名处理器文件。新增工具只需要改这一对文件 —— 一边是 schema 和文档,另一边是具体实现。

服务器(schema + 文档)

扩展(实现)

navigation

src/tools/navigation.js

extension/src/handlers/navigation.js

tabs

src/tools/tabs.js

extension/src/handlers/tabs.js

perception

src/tools/perception.js

extension/src/handlers/perception.js

interaction

src/tools/interaction.js

extension/src/handlers/interaction.js

trusted input

src/tools/trusted-input.js

extension/src/handlers/trusted-input.js

observability

src/tools/observability.js

extension/src/handlers/observability.js

capture

src/tools/capture.js

extension/src/handlers/capture.js

其他一切都是支持性基础设施。

  • bin/custom-chrome-dev-mcp.js - 你注册到 MCP 客户端的可执行文件。它不做任何事,只是启动服务器。

  • src/config.js / extension/src/config.js - 所有可调参数,每侧一个文件。AUTH_TOKEN 和端口必须在两侧保持一致。

  • src/relay/hub-client.js - 以 role:"mcp" 身份连接 hub,hub 不存在时启动它,并把每次工具调用转换为 socket 上的请求/响应。

  • src/hub.js - 持有 ws://127.0.0.1:9876 的常驻中继。它保留唯一的扩展 socket 以及每个会话的客户端,并在它们之间进行多路复用。它对线上传输中的 id 重新打标(因为这些 id 在不同会话间可能冲突);如果端口已被 hub 占用,则自行退出。

  • src/capture/capture-path.js - 写入白名单。所有捕获路径都必须经过它。

  • extension/src/connection.js - hub socket 与心跳。MV3 Service Worker 在空闲约 30 秒后会被销毁,从而会静默丢弃 socket;低于 30 秒的心跳能让这二者保持存活;即使 Service Worker 被强行终止,alarm 也能将其重新唤醒。

  • extension/src/tabs.js - 决定一次调用作用于哪个标签页(显式 tabId > 隐藏标签页 > 活跃标签页),以及 expectUrl 保护机制与禁用列表检查。

  • extension/src/walker-bridge.js + extension/src/page/walker.js - 注入到 payload 隔离世界(ISOLATED world)的脚本,带有稳定的 element-ref 元素引用系统,以及唯一知道如何访问这个脚本的模块。

  • extension/src/cdp/ - session.js(attach/detach、cdp()、元素中心)、keyboard.js(键名 → CDP 按键事件)、dialogs.js(原生对话框策略)、buffers.js(console 与 network 环形缓冲区,每标签页最多 500 条)。

  • extension/src/recording/ - chrome.tabCapture 需要用户手势,而 MCP 调用永远无法提供,因此录制改用 CDP screencast:JPEG 帧被回转并传给离屏的 MediaRecorder(Service Worker 没有 DOM)。

  • test/ - 两行测试套件:一个不需要浏览器的离线 CI 关卡,以及一个驱动真实 Chrome 的浏览器通道。参见 运行测试


项目结构

.
├── bin/
│   └── custom-chrome-dev-mcp.js   # executable entry - register THIS with your client
├── src/
│   ├── server.js                  # composes config + relay + tool registry
│   ├── config.js                  # port, token, capture dir, timeouts
│   ├── hub.js                     # long-lived relay owning :9876
│   ├── relay/
│   │   └── hub-client.js          # session -> hub socket; call()
│   ├── capture/
│   │   └── capture-path.js        # write allowlist for screenshots/recordings
│   └── tools/                     # ONE FILE PER TOOL GROUP - the public surface
│       ├── index.js               # the registry
│       ├── schemas.js             # shared arg shapes + passthrough helper
│       ├── navigation.js
│       ├── tabs.js
│       ├── perception.js
│       ├── interaction.js
│       ├── trusted-input.js
│       ├── observability.js
│       └── capture.js
├── extension/                     # Chrome MV3 extension
│   ├── manifest.json
│   └── src/
│       ├── background.js          # service worker entry - wiring only
│       ├── config.js              # token, banlist, buffer caps, asset paths
│       ├── connection.js          # hub socket + MV3 keepalive heartbeat
│       ├── tabs.js                # tab resolution, pinning, ban check
│       ├── walker-bridge.js       # channel to the injected page script
│       ├── cdp/
│       │   ├── session.js         # attach/detach, cdp(), element centres
│       │   ├── keyboard.js        # key names -> CDP key events
│       │   ├── dialogs.js         # native alert/confirm/prompt policy
│       │   └── buffers.js         # console + network ring buffers
│       ├── recording/
│       │   ├── recorder.js        # CDP screencast -> offscreen encoder
│       │   ├── offscreen.html
│       │   └── offscreen.js       # MediaRecorder host
│       ├── page/
│       │   └── walker.js          # injected DOM driver (ISOLATED world)
│       └── handlers/              # MIRRORS src/tools/ - one file per group
│           ├── index.js           # the handler table + dispatch
│           ├── navigation.js
│           ├── tabs.js
│           ├── perception.js
│           ├── interaction.js
│           ├── trusted-input.js
│           ├── observability.js
│           └── capture.js
└── test/                          # two lanes: offline (CI) + browser
    ├── run.mjs                    # CLI entry
    ├── lib/                       # runner, assertions, bridge, fixtures, session
    ├── fixtures/                  # the fixture pages, as real files
    └── suites/                    # one suite per tool group

致谢

灵感来源于 Chrome DevTools 团队的 Chrome DevTools MCP。这是一个独立的重新实现,与 Google 无关、未获 Google 认可或支持。


许可证

MIT. Copyright (c) 2026 Haba Andrei.

使用它、fork 它、发布它。唯一的条件是:版权声明和许可声明必须随任何实质性副本一起附随。

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to drive a real, logged-in Chrome browser for web automation tasks like navigation, clicking, typing, and screenshotting.
    1
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Drive your real, signed-in Chrome browser from any MCP client, enabling browser automation such as navigation, clicking, typing, and screenshots through standard MCP tools.
    1
  • A
    license
    C
    quality
    A
    maintenance
    MCP server for browser automation that drives Chrome via an extension, preserving login state and offering 45 tools for navigation, interaction, scraping, and screenshots.
    53
    4
    MIT

View all related MCP servers

Related MCP Connectors

  • Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.

  • Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.

  • Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HabaAndrei/custom-chrome-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server