chromium-cdp-mcp
v1.0.0
Published
MCP server (TypeScript) that drives ANY Chromium-based browser (Chrome, Edge, Brave, Opera, Vivaldi, plain Chromium, headless launchers) over the Chrome DevTools Protocol. Pass any --remote-debugging-port and get full automation: navigate, click, fill, sc
Downloads
161
Maintainers
Readme
chromium-cdp-mcp
Model Context Protocol server that lets an
AI assistant drive any Chromium-based browser via the
Chrome DevTools Protocol.
No Puppeteer, no Playwright — a thin layer of native WebSocket directly to the
browser's --remote-debugging-port.
What it is
A standalone MCP server (TypeScript). Once an MCP-aware client (Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Continue.dev, Zed, Codex CLI, Goose, …) loads it, the AI can:
- navigate, click, fill, scroll, type, select options, take screenshots
- run arbitrary JavaScript and receive the real return value
- handle JS dialogs (
alert/confirm/prompt/beforeunload) - click inside cross-origin iframes (Cloudflare Turnstile, reCAPTCHA, embed widgets)
- manage tabs, override the viewport, send raw CDP commands
The server is browser-agnostic: every tool takes a port argument and connects
to whatever Chromium-based browser is listening on that port.
What it is NOT
- Not a profile / fingerprint manager. For antidetect profile creation, proxies,
cookies, and batched profile orchestration use a separate MCP such as
undetectable-local-api-mcp-ts. - Not a browser launcher. You start the browser yourself (or via the antidetect software); this server attaches.
Install
From npm
# in your MCP client config — auto-fetches and runs
npx -y chromium-cdp-mcpFrom source
git clone https://github.com/<your-org>/chromium-cdp-mcp
cd chromium-cdp-mcp
npm install
npm run build
node dist/server.js # or `npm start`Requires Node.js ≥ 22 (native WebSocket).
MCP client config
Same shape for every client. Example (claude_desktop_config.json etc.):
{
"mcpServers": {
"chromium-cdp": {
"command": "npx",
"args": ["-y", "chromium-cdp-mcp"]
}
}
}For a local checkout:
{
"mcpServers": {
"chromium-cdp": {
"command": "node",
"args": ["/absolute/path/to/MCP-CDP/dist/server.js"]
}
}
}Launch a browser with CDP enabled
The server connects to an already-running Chromium instance via its remote debugging port. Start one of these first:
Chrome / Chromium
# Linux / macOS
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-cdp
# Windows (PowerShell or cmd)
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
--remote-debugging-port=9222 ^
--user-data-dir="%TEMP%\chrome-cdp"Microsoft Edge
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" ^
--remote-debugging-port=9222 ^
--user-data-dir="%TEMP%\edge-cdp"Brave / Opera / Vivaldi / Yandex Browser
Same --remote-debugging-port=PORT flag — every Chromium fork supports it.
Puppeteer / Playwright launchers
// Puppeteer
const browser = await puppeteer.launch({
args: ["--remote-debugging-port=9222"],
});
// Playwright (chromium)
const browser = await chromium.launch({
args: ["--remote-debugging-port=9222"],
});Use a fresh profile
--user-data-dir points to a clean profile directory. Without it, Chrome
refuses to enable remote debugging on your default profile (a 122+ security
restriction).
Verify the port is live
curl http://127.0.0.1:9222/json/versionIf you see JSON with webSocketDebuggerUrl, you're ready.
Connection block
Every tool accepts the same connection fields:
| Field | Type | Description |
| -------------- | -------- | ---------------------------------------------------------------------------- |
| port | number | The browser's --remote-debugging-port. Required. |
| host | string | Default 127.0.0.1. |
| target_id | string | Explicit page targetId from list_tabs. Skips auto-pick. |
| url_contains | string | Pick the first page whose URL contains this substring. |
| tab_index | number | Pick page by index in Target.getTargets order. |
Auto-pick (when none of target_id / url_contains / tab_index is given):
the first attached page, falling back to the first page in the target list.
Tools
Connection / tabs
| Tool | What it does |
| ----------- | --------------------------------------------------------------------- |
| connect | Probe /json/version + /json/list. Diagnostic. |
| list_tabs | Target.getTargets. Returns {targetId, type, url, title, attached}.|
| new_tab | Open a new tab. Optional url. Returns the new targetId. |
| close_tab | Close the tab specified by target_id. |
Navigation
| Tool | What it does |
| ---------- | ---------------------------------------------------------------------------- |
| navigate | Page.navigate + best-effort wait for load. Returns final URL. |
| back | history.back(). |
| forward | history.forward(). |
| reload | Page.reload, optional ignore_cache. |
JavaScript
| Tool | What it does |
| --------------- | ------------------------------------------------------------------------------------------------------- |
| evaluate | Runtime.evaluate with awaitPromise=true, returnByValue=true. Returns the actual value. |
| get_page_html | document.documentElement.outerHTML. |
| get_url | location.href. |
DOM interactions
| Tool | What it does |
| -------------------------- | ----------------------------------------------------------------------------------------------- |
| click | Real mouse press+release at the element's center. CSS or XPath selector. |
| click_at | Real mouse press+release at raw viewport (x, y). |
| click_iframe | Real click inside any iframe (cross-origin friendly). dx / dy offsets, default center. |
| click_and_accept_dialog | Click AND auto-handle any alert / confirm / prompt dialog the click triggers. |
| handle_dialog | Handle a currently-open JS dialog. accept, optional prompt_text. |
| fill | Focus + Input.insertText. Clears existing value unless append=true. Supports unicode. |
| select_option | Set <select> value + dispatch input and change events so frameworks react. |
| focus | el.focus(). |
| scroll_to | Scroll element into view (center). Returns its bounding rect. |
| scroll | window.scrollTo(x,y) (absolute) or scrollBy(dx,dy) (relative). |
| press_key | Input.dispatchKeyEvent — Enter / Tab / Escape / arrows / printable chars / unicode. |
| wait_for | Poll for a selector until it appears (or timeout). |
Capture / emulation
| Tool | What it does |
| -------------- | -------------------------------------------------------------------------------------------------- |
| screenshot | Page.captureScreenshot → file. full_page, format (png / jpeg), quality. |
| set_viewport | Emulation.setDeviceMetricsOverride. Mobile emulation. clear: true to reset. |
Escape hatch
| Tool | What it does |
| ----- | -------------------------------------------------------------------------------------------------- |
| raw | Send any CDP method directly. For things not exposed as a dedicated tool: Network.*, Storage.*, Fetch.*, Target.activateTarget, etc. |
Selectors
Any selector starting with / or (/ is treated as XPath. Everything else
is treated as CSS. Examples:
input[name="email"] # CSS
#submit # CSS
//button[contains(.,"Войти")] # XPath
(//a[@class="title"])[3] # XPathQuickstart for an AI assistant
A natural step sequence for filling and submitting a form looks like this:
1. connect { port: 9222 } # confirm browser is alive
2. navigate { port: 9222, url: "https://example.com" } # load page
3. wait_for { port: 9222, selector: "input#email" } # wait for hydration
4. fill { port: 9222, selector: "input#email", text: "[email protected]" }
5. fill { port: 9222, selector: "input#password", text: "<entered by user>" }
6. click_and_accept_dialog
{ port: 9222, selector: "#submit", accept: true }
7. wait_for { port: 9222, selector: ".dashboard" }
8. screenshot { port: 9222, path: "/tmp/dashboard.png" }Integration with antidetect browsers
Antidetect / multi-profile browsers expose a remote-debugging port per running profile. Hand that port to this MCP and it can drive the underlying browser without caring about fingerprinting, proxies, or profile state — those are managed by the antidetect's own MCP / API.
Example: Undetectable + chromium-cdp-mcp
Undetectable's local API (and its
undetectable-local-api-mcp-ts
wrapper) returns a debug_port whenever a profile is started:
// undetectable-local-api-mcp-ts → start_profile response
{
"code": 0,
"data": {
"debug_port": "52967",
"websocket_link": "ws://127.0.0.1:52967/devtools/browser/<id>",
"name": "my-profile"
},
"status": "success"
}Feed 52967 straight into this MCP's tools:
// chromium-cdp-mcp → navigate
{ "port": 52967, "url": "https://example.com" }This split keeps responsibilities clean:
- undetectable-local-api-mcp-ts — create profile, set proxy, set OS/browser, start / stop, batch operations, cookies, fingerprint configs.
- chromium-cdp-mcp — everything the AI does inside the browser session.
The same pattern works for any antidetect browser or headless launcher that
publishes a webSocketDebuggerUrl on a known port (AdsPower, GoLogin,
Multilogin, Puppeteer, Playwright …).
Env
| Variable | Default | Notes |
| ------------- | ------- | ------------------------------------------------ |
| CDP_TIMEOUT | 30 | Per-command timeout in seconds. |
Known limitations
- Native WebSocket required. Node ≥ 22.
- Cross-origin iframe selectors.
click/fill/select_optionuse a selector evaluated against the main page DOM. For elements inside a cross-origin iframe (Cloudflare Turnstile, reCAPTCHA, embedded YouTube …) useclick_iframe(selector resolves to the iframe element, the click lands inside) orclick_atwith raw coordinates. - Dialog handling. Native
alert/confirm/promptblock page JS until handled. Useclick_and_accept_dialogto do both in one call, orhandle_dialogto dismiss an already-open one. - Ephemeral session. Each tool call opens a fresh WebSocket and closes it on return. That keeps the server stateless but means CDP event subscriptions are scoped to a single call (this is fine for the supplied tools — the dialog tool handles its own subscription internally).
⚠️ AI gets full browser control. Once connected, an AI client can drive any tab on the target browser: navigate, click, fill forms, run JS, read page content, take screenshots. Only point this server at browsers you are willing to give the AI full access to. Review tool calls in your MCP client.
License
MIT
