cdp-bridge-mcp-browser-control
Original:🇺🇸 English
Translated
Control and automate real browser sessions through CDP, preserving login state and cookies for LLM-driven interactions
5installs
Sourcearadotso/mcp-skills
Added on
NPX Install
npx skill4agent add aradotso/mcp-skills cdp-bridge-mcp-browser-controlTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →CDP Bridge MCP Browser Control
Skill by ara.so — MCP Skills collection.
Overview
CDP Bridge MCP is a Model Context Protocol server that bridges LLM clients to real, active browser sessions through Chrome DevTools Protocol (CDP) and a companion browser extension. Unlike headless automation tools, it connects to your already-open, already-logged-in browser tabs, preserving authentication state, cookies, and rendered page content.
Key differentiators:
- Reuses real login sessions — no need to re-authenticate or transfer cookies
- Works with current browser state — connects to tabs you already have open
- LLM-optimized page scanning — filters HTML to preserve useful content while reducing tokens
- Automatic CSP handling — falls back to CDP when content security policies block script injection
- Lightweight setup — no separate browser instances or complex configuration
Installation
1. Install the MCP Server
The server is available via PyPI and can be run with :
uvxbash
# Test with stdio mode (default)
uvx cdp-bridge@latest
# Run as HTTP server (for multi-client scenarios)
uvx cdp-bridge@latest --transport streamable-http --port 80002. Load Browser Extension
- Navigate to
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the folder from the repository
src/cdp_bridge/tmwd_cdp_bridge
The extension automatically attempts to connect to (WebSocket) and retries every ~5 seconds if the server isn't running yet.
127.0.0.1:187653. Configure MCP Client
For stdio mode (Claude Desktop, most local clients):
json
{
"mcpServers": {
"cdp-bridge": {
"command": "uvx",
"args": ["cdp-bridge@latest"]
}
}
}For streamable-http mode (shared/Docker deployments):
First start the server:
bash
uvx cdp-bridge@latest --transport streamable-http --port 8000Then configure the client:
json
{
"mcpServers": {
"cdp-bridge": {
"type": "streamableHttp",
"url": "http://127.0.0.1:8000/mcp"
}
}
}Claude Code:
bash
# stdio
claude mcp add cdp-bridge uvx cdp-bridge@latest
# streamable-http
claude mcp add cdp-bridge --transport streamable-http http://127.0.0.1:8000/mcpCodex:
bash
# stdio
codex mcp add cdp-bridge uvx cdp-bridge@latest
# streamable-http
codex mcp add cdp-bridge --transport streamable-http --url http://127.0.0.1:8000/mcpAvailable Tools
| Tool | Purpose |
|---|---|
| List all connected browser tabs with URLs and titles |
| Extract page content as simplified HTML or plain text |
| Run JavaScript in the active tab |
| Change which tab is active for MCP operations |
| Execute multiple CDP/extension commands atomically |
| Poll a JavaScript condition until true or timeout |
| Navigate the active tab to a URL |
| Capture page screenshot as base64 PNG |
| Read cookies for the current domain |
Common Usage Patterns
Get Available Tabs
python
# When user asks: "what tabs do I have open?"
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_get_tabs",
arguments={}
)
# Result structure:
# {
# "tabs": [
# {"id": "tab_0", "url": "https://example.com", "title": "Example Domain"},
# {"id": "tab_1", "url": "https://github.com", "title": "GitHub"}
# ],
# "active_tab": "tab_0"
# }Scan Page Content
python
# Extract simplified HTML (default)
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_scan",
arguments={
"tab_id": "tab_0",
"format": "html" # or "text" for plain text
}
)
# Returns cleaned HTML with scripts/styles removed
# Preserves semantic structure for LLM consumptionHow works:
browser_scan- Removes ,
<script>,<style>, hidden elements<svg> - Keeps text content, links, headings, form controls
- Converts to
<img>[Image: alt_text] - Ideal for reducing token usage while preserving page semantics
Execute JavaScript
python
# Extract data or interact with the page
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_execute_js",
arguments={
"tab_id": "tab_0",
"code": """
return {
title: document.title,
links: Array.from(document.querySelectorAll('a'))
.slice(0, 10)
.map(a => ({href: a.href, text: a.innerText}))
};
"""
}
)
# Result contains the returned objectExecution modes:
- Primary: Uses in MAIN world
chrome.scripting.executeScript - Fallback: Uses CDP if CSP blocks injection
Runtime.evaluate
Wait for Dynamic Content
python
# Wait for element to appear (useful for SPAs)
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_wait",
arguments={
"tab_id": "tab_0",
"condition": "document.querySelector('.dynamic-content') !== null",
"timeout": 10, # seconds
"interval": 0.5 # poll every 500ms
}
)
# Returns True if condition met, raises timeout error otherwiseNavigate and Screenshot
python
# Navigate to a URL
await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_navigate",
arguments={
"tab_id": "tab_0",
"url": "https://example.com"
}
)
# Wait for page load
await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_wait",
arguments={
"tab_id": "tab_0",
"condition": "document.readyState === 'complete'",
"timeout": 30
}
)
# Capture screenshot
screenshot = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_screenshot",
arguments={
"tab_id": "tab_0",
"format": "png" # returns base64-encoded image
}
)Read Cookies
python
# Get cookies for authenticated session analysis
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_cookies",
arguments={
"tab_id": "tab_0"
}
)
# Returns array of cookie objects:
# [{"name": "session_id", "value": "...", "domain": "example.com", ...}]Batch Operations (Advanced)
python
# Execute multiple CDP commands in sequence
result = await use_mcp_tool(
server_name="cdp-bridge",
tool_name="browser_batch",
arguments={
"tab_id": "tab_0",
"commands": [
{
"method": "Runtime.evaluate",
"params": {
"expression": "document.title",
"returnByValue": True
}
},
{
"method": "Network.getCookies",
"params": {}
}
]
}
)
# Returns array of results matching command orderReal-World Examples
Extract Article Content from Logged-In Site
python
# User is already logged in to medium.com
# 1. Find the article tab
tabs = await use_mcp_tool("cdp-bridge", "browser_get_tabs", {})
article_tab = next(t for t in tabs["tabs"] if "medium.com" in t["url"])
# 2. Switch to it
await use_mcp_tool("cdp-bridge", "browser_switch_tab", {"tab_id": article_tab["id"]})
# 3. Extract article text
content = await use_mcp_tool(
"cdp-bridge",
"browser_execute_js",
{
"tab_id": article_tab["id"],
"code": """
const article = document.querySelector('article');
return article ? article.innerText : 'No article found';
"""
}
)Monitor Dashboard Data
python
# User has analytics dashboard open
# Poll for updated metrics every 30 seconds
while True:
metrics = await use_mcp_tool(
"cdp-bridge",
"browser_execute_js",
{
"tab_id": "tab_0",
"code": """
return {
visitors: document.querySelector('.visitor-count')?.innerText,
revenue: document.querySelector('.revenue')?.innerText,
timestamp: Date.now()
};
"""
}
)
# Process metrics...
await asyncio.sleep(30)Fill Form in Authenticated Session
python
# Navigate to form page
await use_mcp_tool(
"cdp-bridge",
"browser_navigate",
{"tab_id": "tab_0", "url": "https://example.com/settings"}
)
# Wait for form to load
await use_mcp_tool(
"cdp-bridge",
"browser_wait",
{
"tab_id": "tab_0",
"condition": "document.querySelector('form#settings') !== null",
"timeout": 10
}
)
# Fill and submit
await use_mcp_tool(
"cdp-bridge",
"browser_execute_js",
{
"tab_id": "tab_0",
"code": """
const form = document.querySelector('form#settings');
form.querySelector('input[name="email"]').value = 'user@example.com';
form.querySelector('input[name="notifications"]').checked = true;
form.submit();
"""
}
)Troubleshooting
Extension Shows "ERR_CONNECTION_REFUSED"
Normal behavior on first load. The extension retries connection every ~5 seconds. Once you invoke any MCP tool (e.g., ), the server starts and the extension connects automatically within seconds.
browser_get_tabsNo Tabs Appear in browser_get_tabs
browser_get_tabs- Verify the extension is loaded and enabled at
chrome://extensions/ - Check the extension's service worker console for connection status
- Ensure MCP server is running (invoke any tool to start it in stdio mode)
- Confirm WebSocket server is listening on
127.0.0.1:18765
JavaScript Execution Fails with CSP Error
The extension automatically falls back to CDP when CSP blocks . If both fail:
Runtime.evaluatechrome.scripting- Check browser console for specific CSP violations
- Try wrapping code in
(function() { ... })() - Avoid accessing restricted APIs (e.g., cross-origin iframes)
Tab IDs Change Unexpectedly
Tab IDs are session-based and reset when:
- The extension reloads
- The browser restarts
- WebSocket reconnects
Always call before targeting a specific tab if state may have changed.
browser_get_tabsHigh Token Usage from browser_scan
browser_scan- Use for plain text extraction (fewer tokens)
"format": "text" - Limit scope by executing JS to extract specific DOM subtrees first:
python
await use_mcp_tool(
"cdp-bridge",
"browser_execute_js",
{
"tab_id": "tab_0",
"code": "document.body.innerHTML = document.querySelector('main').innerHTML"
}
)
# Then scan the reduced page
await use_mcp_tool("cdp-bridge", "browser_scan", {"tab_id": "tab_0"})Configuration Options
Server Launch Arguments
bash
uvx cdp-bridge@latest [OPTIONS]
Options:
--transport [stdio|streamable-http] Transport mode (default: stdio)
--port INTEGER HTTP port for streamable-http mode (default: 8000)Extension Configuration
Edit to customize:
src/cdp_bridge/tmwd_cdp_bridge/background.jsjavascript
// WebSocket server address
const WS_URL = 'ws://127.0.0.1:18765';
// Reconnection interval (ms)
const RECONNECT_INTERVAL = 5000;Architecture Notes
- Transport modes: for single-client (subprocess),
stdiofor multi-client (persistent server)streamable-http - Extension communication: WebSocket (primary) + HTTP long-polling (fallback)
- JavaScript execution: MAIN world via , falls back to CDP for CSP-restricted pages
chrome.scripting - Session management: Each connected tab gets a unique session ID, managed by
TMWebDriver
Requirements
- Python 3.10+
- Chrome or Chromium-based browser
- Browser extension loaded in developer mode
- Network access to (WebSocket) and
127.0.0.1:18765(HTTP)127.0.0.1:18766