mcp-server-webdriver
# mcp-server-webdriver

[](LICENSE)

MCP Server that lets AI agents control a real web browser via **Selenium WebDriver** (Firefox + geckodriver).
Built with [FastMCP](https://gofastmcp.com).
---
## What it does
The server eliminates the copy-paste loop between the browser and the AI assistant.
Instead of opening DevTools, copying errors, pasting them into a chat, and repeating,
the assistant opens the browser itself, navigates, captures errors and screenshots,
and diagnoses the problem directly.
```
You: "Why is the checkout button broken on /cart?"
AI: browser_open → browser_navigate("/cart")
→ devtools_report # JS errors? network failures?
→ browser_screenshot # what does it look like?
→ devtools_computed_css("button#checkout") # hidden? wrong z-index?
→ "The button has pointer-events: none — overridden by .disabled class
applied when cart.js fails to load (404 on /static/cart.js)."
```
---
## Requirements
| Dependency | Version |
|---|---|
| Python | ≥ 3.11 |
| [FastMCP](https://pypi.org/project/fastmcp/) | ≥ 2.10 |
| [selenium](https://pypi.org/project/selenium/) | ≥ 4.0 |
| Firefox | any recent |
| [geckodriver](https://github.com/mozilla/geckodriver) | ≥ 0.34 |
---
## Installation
### Recommended — Debian package from VitexSoftware repository
```bash
sudo curl -fsSL http://repo.vitexsoftware.com/KEY.gpg -o /usr/share/keyrings/vitexsoftware-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/vitexsoftware-archive-keyring.gpg] http://repo.vitexsoftware.com trixie main backports" \
| sudo tee /etc/apt/sources.list.d/vitexsoftware.list
sudo apt update
sudo apt install mcp-server-webdriver
```
The `backports` component is required, not optional: `python3-mcp` (a
`python3-fastmcp` dependency) needs `python3-jsonschema >= 4.20.0`, which is
newer than the version Debian trixie ships in `main` — it's only available
in `backports`. Without it, `apt install` fails with an unmet-dependency
error on `python3-jsonschema`.
This installs `gecko-driver`, `python3-selenium`, `python3-fastmcp`, and
`mcp-server-webdriver` in a single step.
### Alternative — system package manager
```bash
# Debian/Ubuntu (official repos — may be older geckodriver):
sudo apt install firefox-geckodriver
# macOS:
brew install geckodriver
# Rust / cargo (build from source):
cargo install geckodriver
```
Then install Python dependencies:
```bash
pip install fastmcp selenium
```
### Fallback — webdriver-manager (auto-download)
```bash
pip install fastmcp selenium webdriver-manager
# geckodriver is downloaded automatically on first browser_open call
```
---
## Usage
```
mcp-server-webdriver [OPTIONS]
OPTIONS
-P <profile> Start Firefox with a named profile
--profile <path> Start Firefox with a profile directory at <path>
-h, --help Show help and exit
```
The server speaks MCP over stdin/stdout and is launched automatically
by the MCP client — not by hand.
---
## Use cases
### Debug a broken page
Ask: *"Why does /dashboard show a blank screen?"*
The assistant will:
1. `browser_open` — open Firefox headlessly
2. `browser_navigate` — go to `/dashboard`
3. `devtools_report` — get JS errors, console output, and failed network resources in one call
4. `browser_screenshot` — see what the page actually looks like
5. Explain the root cause from the combined evidence
`devtools_report` is the primary diagnostic tool — equivalent to opening the Console
and Network tabs in DevTools and reading them simultaneously.
---
### Diagnose a CSS / layout problem
Ask: *"The sidebar overlaps the content area on mobile. Why?"*
1. `browser_open` — open the page
2. `browser_screenshot` — capture the broken layout
3. `devtools_computed_css(".sidebar")` — check `position`, `width`, `z-index`, `overflow`
4. `devtools_css_variables("--")` — verify design tokens loaded correctly
5. `devtools_network_failed` — check whether any stylesheet failed to load
---
### Automate a login flow
Ask: *"Log into the app at /login with user=admin, password=secret and screenshot the dashboard."*
1. `browser_open` — start the browser
2. `browser_navigate` — go to `/login`
3. `browser_fill("#username", "admin")` — type username
4. `browser_fill("#password", "secret")` — type password
5. `browser_press_key("enter")` — submit the form
6. `browser_wait("#dashboard", condition="visible")` — wait for redirect
7. `browser_screenshot` — capture the result
---
### Inject a session cookie to skip login
Ask: *"Check the admin panel using my existing session token."*
1. `browser_open` — start the browser
2. `browser_navigate` — go to the app's root so the cookie domain matches
3. `browser_set_cookie("session", "<token>")` — inject the auth cookie
4. `browser_navigate` — now navigate to the protected page
5. `browser_screenshot` — confirm access
---
### Enumerate page content
Ask: *"List all the navigation links on the homepage."*
1. `browser_open` + `browser_navigate` — open the page
2. `browser_find_elements("nav a")` — get all links with their text, href, and visibility
3. Return the structured list
---
### Interact with hover menus
Ask: *"Click the third item in the Products dropdown."*
1. `browser_hover(".nav-products")` — trigger the `:hover` state that reveals the dropdown
2. `browser_wait(".dropdown-menu", condition="visible")` — wait for animation
3. `browser_find_elements(".dropdown-menu a")` — list the items
4. `browser_click(".dropdown-menu a:nth-child(3)")` — click the right one
---
### Test a multi-step form
Ask: *"Fill out the registration form and submit it."*
1. `browser_fill("#first-name", "Alice")`
2. `browser_fill("#last-name", "Smith")`
3. `browser_fill("#email", "alice@example.com")`
4. `browser_select("#country", "Czech Republic")`
5. `browser_press_key("tab")` — move focus to next field
6. `browser_click("button[type=submit"]")`
7. `browser_wait(".success-message", condition="visible")`
8. `devtools_report` — check for any JS errors or failed API calls during submission
---
### Handle JS dialogs
Ask: *"Click the Delete button and confirm the dialog."*
1. `browser_click("#delete-btn")`
2. `browser_accept_dialog` — click OK on the `confirm("Are you sure?")`
3. `browser_wait(".deleted-notice", condition="present")`
---
### Scroll and capture a long page
Ask: *"Screenshot the footer of the page."*
1. `browser_open` + `browser_navigate`
2. `browser_scroll("footer")` — scroll the footer element into view
3. `browser_screenshot("footer")` — capture just the footer element
Or scroll by offset to trigger lazy-loaded content:
1. `browser_scroll(by=True, y=1000)` — scroll down 1000 px
2. `browser_wait(".lazy-section", condition="visible")` — wait for lazy content
3. `browser_screenshot` — capture the now-loaded content
---
### Use a real Firefox profile (stay logged in)
Configure the server with a named profile that already has your session:
```json
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver",
"args": ["-P", "work"]
}
}
}
```
The browser starts with your existing cookies, saved passwords, and extensions.
Ask: *"Check my GitHub notifications."* — no login step needed.
Profile selection is intentionally a server-launch-time setting (`-P`/`--profile`
flags or `FIREFOX_PROFILE`/`FIREFOX_PROFILE_DIR` env vars) rather than a
`browser_open` tool parameter. That keeps the choice to expose a real,
logged-in profile to an agent in the hands of whoever configures the MCP
client — not something an agent (or a prompt injected via a visited page)
can request at runtime.
---
### Audit network performance
Ask: *"Which resources on /shop are slowest to load?"*
1. `browser_open` + `browser_navigate("/shop")`
2. `devtools_network_all(slow_ms=500, limit=20)` — requests over 500 ms, capped at 20 entries
3. Report the slowest assets with their URLs, types, and durations
---
## Available Tools
### Session management
| Tool | Description |
|---|---|
| `browser_open` | Open Firefox (URL optional, default `about:blank`); accepts `width`, `height`, `user_agent` for mobile emulation |
| `browser_close` | Quit the browser session |
| `browser_status` | Session state, geckodriver version, BiDi status, current viewport size, buffer counts |
| `browser_set_viewport` | Resize the viewport mid-session (e.g. 390×844 for iPhone 14) |
### Navigation
| Tool | Description |
|---|---|
| `browser_navigate` | Navigate to a URL (bare hostnames get `https://`) |
| `browser_back` | Go back in history |
| `browser_forward` | Go forward in history |
| `browser_refresh` | Reload the current page |
### Page inspection
| Tool | Description |
|---|---|
| `browser_screenshot` | Full-page or element PNG screenshot |
| `browser_get_title` | Current page `<title>` |
| `browser_get_url` | Current URL |
| `browser_get_source` | Raw HTML source (wrapped as untrusted content — see [Security](#security)) |
| `browser_get_text` | Visible text (whole page or CSS selector; wrapped as untrusted content) |
| `browser_get_attribute` | Value of an HTML attribute on an element (wrapped as untrusted content) |
| `browser_find_elements` | List all elements matching a CSS selector |
### Interaction
| Tool | Description |
|---|---|
| `browser_click` | Click element (CSS selector) |
| `browser_fill` | Type text into an input field (clears first by default) |
| `browser_select` | Select `<option>` in a `<select>` dropdown |
| `browser_execute_js` | Run JavaScript — returns JSON |
| `browser_wait` | Wait: `visible` / `clickable` / `present` / `text:<str>` |
| `browser_scroll` | Scroll to coords, by offset, or element into view |
| `browser_press_key` | Send `enter` / `tab` / `escape` / arrow / F-keys |
| `browser_hover` | Hover mouse over element (`:hover` states, tooltips, dropdowns) |
| `browser_switch_frame` | Switch into `<iframe>` or back to main document |
### Dialogs & cookies
| Tool | Description |
|---|---|
| `browser_accept_dialog` | Accept a JS `alert()` / `confirm()` / `prompt()` |
| `browser_dismiss_dialog` | Dismiss a JS `confirm()` / `prompt()` |
| `browser_get_cookies` | Read all cookies for the current page |
| `browser_set_cookie` | Inject a cookie (auth tokens, session IDs) |
### DevTools (require BiDi — Firefox + geckodriver ≥ 0.34)
| Tool | Description |
|---|---|
| `devtools_report` | **Main diagnostic tool** — JS errors + console + failed/slow network |
| `devtools_js_errors` | JavaScript exceptions only (entries carry an `untrusted` marker) |
| `devtools_console` | Console output (log / warn / error / info / debug; entries carry an `untrusted` marker) |
| `devtools_network_failed` | Failed resources (4xx, 5xx, DNS errors) |
| `devtools_network_all` | All network requests (supports `limit=` and filters) |
| `devtools_clear` | Clear buffered DevTools data (use before navigating) |
| `devtools_enable_bidi` | Attach BiDi listeners to a running session |
| `devtools_computed_css` | Computed CSS properties of an element |
| `devtools_element_info` | Bounding box, visibility, attributes, aria, outerHTML |
| `devtools_css_variables` | CSS custom properties (`--var`) in scope |
---
## Security
Anything a visited page contains — HTML, visible text, attribute values,
console output, JS error messages, web storage — is attacker-controllable if
the page is malicious or compromised. Tools that relay this content back to
the calling agent (`browser_get_source`, `browser_get_text`,
`browser_get_attribute`, `devtools_console`, `devtools_js_errors`,
`devtools_report`, `browser_get_storage`) wrap it in an explicit
`-----BEGIN/END UNTRUSTED CONTENT-----` envelope (or an `untrusted` field on
structured entries), so an agent has a structural signal that the content is
data to read, never an instruction to follow — even if it's phrased as one.
This defends against indirect prompt-injection / agent-hijacking attacks
delivered via ordinary page content.
This complements the existing Firefox-profile lockdown (see
[*Use a real Firefox profile*](#use-a-real-firefox-profile-stay-logged-in)):
both exist because a page — or an instruction injected via one — should never
be able to silently expand what the agent can do or see.
The server also defaults to **read-only mode**: page-mutating tools
(`browser_click`, `browser_fill`, `browser_upload_file`, `browser_select`,
`browser_execute_js`, `browser_press_key`, `browser_accept_dialog`,
`browser_dismiss_dialog`, `browser_set_cookie`, `browser_set_storage`,
`browser_clear_storage`) refuse to run unless `WEBDRIVER_READONLY=false` is
set. Navigation and session/viewport management (`browser_open`,
`browser_navigate`, `browser_back`/`forward`/`refresh`, `browser_scroll`,
`browser_hover`, `browser_wait`, `browser_switch_frame`, `browser_close`,
`browser_set_viewport`) are unaffected, since they're needed to reach the
page a read-only session inspects.
---
## Environment variables
| Variable | Default | Description |
|---|---|---|
| `GECKODRIVER_PATH` | _(unset)_ | Absolute path to geckodriver binary (highest priority) |
| `GECKODRIVER_AUTO_INSTALL` | `true` | Set to `false` to disable webdriver-manager fallback |
| `FIREFOX_BINARY` | _(unset)_ | Path to a custom Firefox executable |
| `FIREFOX_PROFILE` | _(unset)_ | Named Firefox profile — same as `-P` |
| `FIREFOX_PROFILE_DIR` | _(unset)_ | Profile directory path — same as `--profile` |
| `WEBDRIVER_READONLY` | `true` | Set to `false` to allow page-mutating tools (click/fill/upload/select/execute_js/press_key/dialogs/cookies/storage). Empty/unset values stay read-only (fail-closed). |
---
## geckodriver resolution order
| # | Source | Configure via |
|---|---|---|
| 1 | `GECKODRIVER_PATH` env variable | Absolute path to the binary |
| 2 | **System PATH** (default) | `apt install gecko-driver` from repo.vitexsoftware.com |
| 3 | webdriver-manager auto-download | Fallback; disable with `GECKODRIVER_AUTO_INSTALL=false` |
---
## MCP client configuration
Minimal config:
```json
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver"
}
}
}
```
With a named Firefox profile (stays logged in, uses saved passwords):
```json
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver",
"args": ["-P", "work"]
}
}
}
```
With a profile directory and explicit geckodriver path:
```json
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver",
"args": ["--profile", "/home/user/.mozilla/firefox/abc123.dev"],
"env": {
"GECKODRIVER_PATH": "/usr/bin/geckodriver"
}
}
}
}
```
---
## Running tests
```bash
# Unit tests only (no browser required):
pytest tests/ -m "not integration"
# Live capability scenario (opens about:blank when geckodriver+Firefox exist):
python tests/live_capability_scenario.py
# All tests including browser integration:
pytest tests/
```
The live scenario respects `WEBDRIVER_READONLY=true` (default): mutating tools
must refuse, while session/read tools open `about:blank`, call
`browser_status` / `browser_get_title` / `browser_get_url` / `browser_get_source`,
then `browser_close`. If `/usr/bin/geckodriver` or Firefox is missing, browser
steps are skipped gracefully (exit 0).
---
## Related MCP Servers by VitexSoftware
| Server | Description |
|---|---|
| [abraflexi-mcp-server](https://github.com/VitexSoftware/abraflexi-mcp-server) | AbraFlexi accounting/ERP integration — invoices, contacts, products, bank transactions |
| [mastodon-mcp-server](https://github.com/VitexSoftware/mastodon-mcp-server) | Mastodon integration — timelines, posting, account management, search |
| [semaphore-mcp-server](https://github.com/VitexSoftware/semaphore-mcp-server) | Semaphore UI integration — manage Ansible, Terraform and other automation workflows |
---
## License
MIT
TDQS
Scored across 43 tools
Most tools target a distinct resource or action, and the browser_* vs devtools_* prefixes make intent clear. The main overlap is devtools_report, which aggregates what devtools_js_errors, devtools_console, and devtools_network_failed already expose, but their overview-vs-focused relationship is explained well enough.
Names are snake_case and follow a readable prefix pattern: browser_<verb-like action> and devtools_<inspection/data>. Minor deviations like browser_status, browser_screenshot, and the noun-style devtools_* names keep it from being fully verb_noun, but the convention is predictable.
43 tools is above the 25+ threshold and creates a heavy selection surface for an agent, even though the webdriver domain is broad. Some devtools_* getters could be consolidated, and the set would benefit from tighter scoping or splitting browser and devtools concerns.
The surface covers navigation, interaction, waiting, frames, dialogs, cookies/storage, screenshots, and detailed devtools diagnostics. Notable gaps like multi-tab/window management and drag-and-drop are missing, but most core webdriver workflows are fully supported.