seleniumbase-mcp
by ileonzin
README.md
# seleniumbase-mcp — SeleniumBase CDP Mode as an MCP server
An [MCP](https://modelcontextprotocol.io) server that exposes SeleniumBase's
**CDP Mode** (undetected, driverless Chrome automation) to any MCP client —
Claude Code, Claude Desktop, Cursor, or your own agent.
Beyond the usual click/type/screenshot tools, it ships a **JavaScript
reverse-engineering toolkit**: grep across a page's bundles, dump a function's
source, hook a function to record its arguments, and intercept `fetch`/XHR to
discover a site's private API — all without pulling megabytes of minified code
into the model's context.
*Também disponível em [português](README.pt-BR.md).*
## Why CDP Mode
SeleniumBase's CDP Mode drives Chrome over the DevTools Protocol with no
WebDriver attached, which gets past most bot detection that trivially flags
Selenium and Playwright. This server keeps that property intact and adds
multi-browser session management on top.
## Features
- **Many browsers at once.** Every browser is named by a `browser_id` you
choose, so one agent can drive several, and several agents can share one
server without stepping on each other.
- **Thread-per-browser isolation.** Each browser owns a dedicated thread and
event loop — required, because SeleniumBase's CDP layer calls
`loop.run_until_complete` internally and would deadlock inside FastMCP's loop.
- **Context-frugal reverse engineering.** The RE tools run JavaScript *inside
the page* and return only the match, the slice, or the payload you asked for.
- **stdio or HTTP.** Run one server per client for full isolation, or one shared
HTTP server for several clients.
## Install
```bash
git clone https://github.com/ileonzin/seleniumbase-mcp.git
cd seleniumbase-mcp
pip install -r requirements.txt
```
Requires Python 3.10+ and a local Chrome installation.
## Register with a client
### Claude Code
```bash
claude mcp add seleniumbase-cdp -- python /absolute/path/to/seleniumbase-mcp/sb_mcp_server.py
```
### Claude Desktop / any client with a JSON config
```json
{
"mcpServers": {
"seleniumbase-cdp": {
"command": "python",
"args": ["/absolute/path/to/seleniumbase-mcp/sb_mcp_server.py"]
}
}
}
```
Restart the client; the tools appear as `mcp__seleniumbase-cdp__*`.
### Shared HTTP server (several clients, one browser pool)
```bash
python sb_mcp_server.py --http --port 8765
```
```bash
claude mcp add --transport http seleniumbase-cdp http://127.0.0.1:8765/mcp
```
Give each client its own `browser_id` prefix (`agent1-main`, `agent2-main`) so
they don't collide.
## Tools
### Lifecycle
| Tool | What it does |
|------|--------------|
| `browser_open(browser_id, url, headless, incognito, proxy)` | Launch a CDP-mode Chrome under a name you pick |
| `browser_close(browser_id)` | Close it and free the Chrome process |
| `browser_list()` | Names of all open browsers |
### Navigation and interaction
| Tool | What it does |
|------|--------------|
| `navigate(browser_id, url)` | Go to a URL, returns the landed URL |
| `click(browser_id, selector)` | Click the first CSS match |
| `type_text(browser_id, selector, text)` | Type into a field |
| `get_text(browser_id, selector)` | Visible text of an element |
| `get_html(browser_id)` | Full page source |
| `current_url(browser_id)` | Current URL |
| `eval_js(browser_id, script)` | Evaluate JS, return the value |
| `wait_for_element(browser_id, selector, timeout)` | Wait until visible |
| `screenshot(browser_id)` | PNG returned as an image |
### Reverse-engineering toolkit
| Tool | What it does |
|------|--------------|
| `list_scripts(browser_id)` | Inventory of `<script>` tags — URLs, sizes, same-origin flags. No content, just the map. |
| `get_script(browser_id, target, offset, length)` | Read one script by index or URL substring, paginated. Same-origin only (CORS blocks the rest). |
| `grep_js(browser_id, pattern, flags, max_hits, max_script_kb)` | Regex every inline and same-origin script; returns matching lines with file and line number. |
| `get_fn_source(browser_id, expression)` | `toString()` any live function — `window.app.encrypt`, `JSON.parse`, anything defined. |
| `hook_fn(browser_id, path)` | Wrap a function so its calls are recorded (args + return). Cleared on navigation. |
| `get_hook_log(browser_id, clear)` | Last 200 recorded calls. |
| `network_log(browser_id, enable)` | Install `fetch`/XHR interceptors to record URL, method, body, status. |
| `get_network(browser_id, clear)` | Last 200 captured requests. |
## Example: find out how a site signs its API calls
```
browser_open(browser_id="re", url="https://target.example")
network_log(browser_id="re") # start watching traffic
click(browser_id="re", selector="#search-button") # trigger the action
get_network(browser_id="re") # → POST /api/v2/search, X-Sig header
grep_js(browser_id="re", pattern="X-Sig|signature")
get_fn_source(browser_id="re", expression="window.__app.sign")
hook_fn(browser_id="re", path="__app.sign") # capture real inputs
get_hook_log(browser_id="re")
browser_close(browser_id="re")
```
The model sees a handful of matched lines and one function body instead of a
3 MB bundle.
## Notes and limits
- **Prefer `headless=False` on anti-bot sites.** CDP Mode is meaningfully more
stealthy with a visible window.
- **Cross-origin scripts are unreadable** by `get_script` and `grep_js` — the
page's own `fetch` is subject to CORS. They're reported under `unreadable`.
- **`hook_fn` captures the return value synchronously.** For an `async`
function you get the Promise object, not its resolution; the arguments — the
usually interesting part — come through fine.
- **Hooks die on reload.** Both `hook_fn` and `network_log` patch live objects,
so navigation or a reload wipes them. Re-install after navigating.
- **Every `browser_open` starts a real Chrome process.** Close what you open.
## Intended use
Built for authorized work: testing your own sites, scraping where you have
permission, security research, and understanding APIs you're allowed to
integrate with. Respect the terms of service and the law of whatever you point
it at.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues