chrome-devtools-mcp-mux

chrome-devtools-mcp-mux

Enables multiple MCP clients to share a single Chrome instance while maintaining isolated tab sessions for each client. It acts as a multiplexer between clients and the chrome-devtools-mcp server, preventing tab conflicts across different sessions like Claude Code windows.

Category
访问服务器

README

chrome-devtools-mcp-mux

CI

Drop-in replacement for chrome-devtools-mcp that lets many MCP clients share one Chrome instance and one profile without stepping on each other's tabs. Each client — a separate Claude Code session, for example — gets its own isolated set of tabs, while they all run against the same single browser and profile.

<p align="center"> <img src="demo/artifacts/mux-demo.gif" alt="Two MCP clients, one Chrome, each seeing only its own tab" width="100%"> </p>

<p align="center"><em>Idle waits auto-compressed to 0.5 s each. Full-resolution mp4s in <a href="demo/artifacts/">demo/artifacts/</a>: <a href="demo/artifacts/mux-demo-condensed.mp4">condensed (9 s)</a> · <a href="demo/artifacts/mux-demo.mp4">full 2:26</a>. Reproducer in <a href="demo/">demo/</a>.</em></p>

What problem does this solve

chrome-devtools-mcp exposes Chrome DevTools to an MCP client. It works perfectly for one client, but if two clients connect at once (two Claude Code windows, a Claude Code plus a Gemini CLI, a coding agent plus a test runner — anything using the same config) they step on each other's tabs: list_pages shows everything, select_page races, new_page lands in the wrong window, close_page can shut down another client's work.

cdmcp-mux sits between the clients and chrome-devtools-mcp and tracks who owns which tab. Each client sees only its own tabs; cross-client collisions are rejected before they ever reach the browser.

vs. vanilla chrome-devtools-mcp

Concern chrome-devtools-mcp chrome-devtools-mcp-mux
Config shape npx -y chrome-devtools-mcp@latest npx -y chrome-devtools-mcp-mux@latest — literally one token different
Tools exposed to clients full vanilla surface identical (pageId stays stripped; isolatedContext still exposed as opt-in)
Chrome profile / cookies / logins / extensions one --userDataDir same --userDataDir, no forced incognito
Headless vs. headful by default headful headful (matches vanilla; force headless with CDMCP_MUX_HEADLESS=true)
Single client running alone fine fine — no behavior change, no overhead worth worrying about
Two+ clients against one Chrome collide: shared list_pages, racy select_page, cross-client close_page isolated at the tool layer: list_pages per-client, cross-client tool calls rejected
new_page's optional isolatedContext passes through passes through (you can still opt in to per-tab isolation if you want it)
CLI flag pass-through (--viewport, --browserUrl, etc.) ✗ not yet — see drop-in gaps
--autoConnect / attaching to a running Chrome ✗ not yet
Upstream version latest every npx pins a tested chrome-devtools-mcp version per release
Maintained by Chrome DevTools team this repo (thin wrapper over upstream)
Runtime overhead zero one long-lived daemon process, one unix socket hop per tool call

Rule of thumb: if you only ever run one MCP client at a time, stick with vanilla. If you run two or more — different Claude Code sessions, an agent plus your own debugger, parallel test runners, etc. — the mux stops them from corrupting each other's state.

Drop-in gaps

A small number of vanilla behaviors aren't plumbed through the mux yet. Open an issue if one matters to you:

  • Arbitrary CLI args passed in "args" (e.g. --viewport=1920x1080) are currently ignored. The mux spawns upstream with a fixed set of flags.
  • --browserUrl, --wsEndpoint, --autoConnect — the mux always launches its own Chromium; it doesn't yet know how to attach to one that's already running.
  • Upstream version is pinned per release. If upstream ships a new tool, the mux needs a version bump to expose it.

For anything not on this list, the mux is behaviorally indistinguishable from vanilla for a single client, and strictly better for many.

Install and configure

If your .mcp.json currently looks like this (the canonical upstream setup):

{
  "mcpServers": {
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest"]
    }
  }
}

change chrome-devtools-mcp@latest to chrome-devtools-mcp-mux@latest and you're done:

{
  "mcpServers": {
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp-mux@latest"]
    }
  }
}

The first client to connect auto-spawns a shared daemon; subsequent clients attach to the same daemon. Each gets its own view of tabs, but they all live in the same Chrome profile — so your cookies, logins, and extensions are the same as with vanilla chrome-devtools-mcp.

<details> <summary>Local-dev install (building from source)</summary>

git clone https://github.com/ochen1/chrome-devtools-mcp-mux
cd chrome-devtools-mcp-mux
npm install
npm run build
npm link           # exposes `cdmcp-mux` on PATH

Then "command": "cdmcp-mux" in .mcp.json. </details>

How to verify it's working

Start two MCP clients with the config above. In each, ask the model to:

  1. Open a different URL via new_page.
  2. Run list_pages.

Each client should see only its own page. On the host, run cdmcp-mux status to see both contexts side-by-side in the daemon.

For a full scripted demo with a recorded video, see demo/.

Environment variables (optional)

Variable Purpose
CDMCP_MUX_CHROMIUM Chromium binary (defaults to bundled Puppeteer)
CDMCP_MUX_USER_DATA_DIR Override Chrome profile directory
CDMCP_MUX_SOCKET Override unix socket path for the daemon
CDMCP_MUX_HEADLESS true forces headless (default: headful, matching vanilla chrome-devtools-mcp)
MCP_MUX_DEBUG 1 logs every rewrite diff

Debugging

All out-of-band; the mux never exposes debug tools to MCP clients.

Command What it does
cdmcp-mux status daemon pid, upstream state, contexts, owned pages
cdmcp-mux tail [-f] stream the structured mux log

The log lives at ~/.local/state/cdmcp-mux/mux.log.

How it works

flowchart TB
    subgraph clients["one process per MCP client"]
      direction LR
      C1["Claude Code #1"] -- "stdio (MCP)" --> S1["cdmcp-mux shim"]
      C2["Claude Code #2"] -- "stdio (MCP)" --> S2["cdmcp-mux shim"]
    end

    subgraph shared["shared — auto-spawned on first connect"]
      direction TB
      D["mux daemon<br/><i>per-connection ownership table</i><br/>(socket fd → ctxId → owned pageIds)"]
      U["chrome-devtools-mcp subprocess<br/><code>--experimentalPageIdRouting</code><br/><code>--userDataDir &lt;fixed&gt;</code>"]
      B["Chromium<br/><i>one instance, one profile<br/>cookies shared across clients</i>"]
      D -- "stdio (MCP)<br/>rewrite + filter" --> U
      U -- "CDP" --> B
    end

    S1 -- "unix socket" --> D
    S2 -- "unix socket" --> D

    classDef client fill:#e3f2fd,stroke:#1976d2
    classDef shim fill:#fff3e0,stroke:#f57c00
    classDef core fill:#f3e5f5,stroke:#7b1fa2
    classDef browser fill:#e8f5e9,stroke:#388e3c
    class C1,C2 client
    class S1,S2 shim
    class D,U core
    class B browser

Each MCP client spawns its own cdmcp-mux shim (that's how .mcp.json works — one child per client). The shim is a pure byte pipe between the client's stdio and a unix socket; the first shim to connect auto-spawns the shared daemon, later shims attach to it. The daemon owns one chrome-devtools-mcp subprocess driving one Chromium with one --userDataDir.

The daemon advertises the same tool schemas as vanilla chrome-devtools-mcp. Every connection gets its own ownership table of pageIds it created; the daemon filters list_pages to that set and rejects cross-context calls to close_page, select_page, and other page-scoped tools. pageId is stripped from the advertised schemas and re-injected internally on every tools/call, so concurrent calls from different clients always target the right tab — backed by upstream's --experimentalPageIdRouting.

Tabs are not forced into isolated browser contexts — all clients share the same Chrome profile, so your cookies and logins work the same as with vanilla chrome-devtools-mcp. The isolatedContext parameter on new_page stays exposed exactly like upstream: if a client wants an incognito-style context, it passes it, and the mux forwards it verbatim. When a client disconnects, the daemon closes every tab it owned and the rest keep running.

Development notes

This project was written end-to-end by a Claude-Code agent in a single working session, driven by live conversational requirements. The full test plan is tiered for functional correctness (58 tests, ~19 s, all passing), and the multiplexer was then visually demonstrated via a VNC-automated reproducer.

For the PRD-to-test mapping see DEMO.md. For the full agentic development log — requirements discovery, architecture iteration, test tiering, and the three takes of the video demo — see demo/README.md.

Testing

# requires a Chromium binary; the smoke/e2e tests need it
CDMCP_MUX_CHROMIUM=/usr/bin/chromium npm test

Expected: 8 files, 58 tests, all passing.

Releasing

CI runs on every push and PR against main using Node 22 and 24, building, typechecking, and executing the full 58-test suite (including the real-Chromium smoke and binary-e2e tests).

Publishing is automated via .github/workflows/publish.yml, which runs on a GitHub release being published:

  1. Bump version in package.json, commit, tag as v<version>.
  2. gh release create v<version> --generate-notes.
  3. The workflow builds, tests, and runs npm publish with npm provenance (signed via GitHub OIDC, the workflow has id-token: write).

NPM_TOKEN is the only required repo secret. The package is published with publishConfig.provenance: true, so the --provenance flag is implicit. Once this repo is registered as a trusted publisher at npmjs.com, the NPM_TOKEN secret can be removed entirely.

License

Apache-2.0 — see LICENSE. Same as upstream chrome-devtools-mcp.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选