mcp-server-webdriver
mcp-server-webdriver
MCP Server that lets AI agents control a real web browser via Selenium WebDriver (Firefox + geckodriver).
Built with FastMCP.
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 |
≥ 2.10 | |
≥ 4.0 | |
Firefox | any recent |
≥ 0.34 |
Installation
Recommended — Debian package from VitexSoftware repository
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-webdriverThe 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
# Debian/Ubuntu (official repos — may be older geckodriver):
sudo apt install firefox-geckodriver
# macOS:
brew install geckodriver
# Rust / cargo (build from source):
cargo install geckodriverThen install Python dependencies:
pip install fastmcp seleniumFallback — webdriver-manager (auto-download)
pip install fastmcp selenium webdriver-manager
# geckodriver is downloaded automatically on first browser_open callUsage
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 exitThe 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:
browser_open— open Firefox headlesslybrowser_navigate— go to/dashboarddevtools_report— get JS errors, console output, and failed network resources in one callbrowser_screenshot— see what the page actually looks likeExplain 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?"
browser_open— open the pagebrowser_screenshot— capture the broken layoutdevtools_computed_css(".sidebar")— checkposition,width,z-index,overflowdevtools_css_variables("--")— verify design tokens loaded correctlydevtools_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."
browser_open— start the browserbrowser_navigate— go to/loginbrowser_fill("#username", "admin")— type usernamebrowser_fill("#password", "secret")— type passwordbrowser_press_key("enter")— submit the formbrowser_wait("#dashboard", condition="visible")— wait for redirectbrowser_screenshot— capture the result
Inject a session cookie to skip login
Ask: "Check the admin panel using my existing session token."
browser_open— start the browserbrowser_navigate— go to the app's root so the cookie domain matchesbrowser_set_cookie("session", "<token>")— inject the auth cookiebrowser_navigate— now navigate to the protected pagebrowser_screenshot— confirm access
Enumerate page content
Ask: "List all the navigation links on the homepage."
browser_open+browser_navigate— open the pagebrowser_find_elements("nav a")— get all links with their text, href, and visibilityReturn the structured list
Interact with hover menus
Ask: "Click the third item in the Products dropdown."
browser_hover(".nav-products")— trigger the:hoverstate that reveals the dropdownbrowser_wait(".dropdown-menu", condition="visible")— wait for animationbrowser_find_elements(".dropdown-menu a")— list the itemsbrowser_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."
browser_fill("#first-name", "Alice")browser_fill("#last-name", "Smith")browser_fill("#email", "alice@example.com")browser_select("#country", "Czech Republic")browser_press_key("tab")— move focus to next fieldbrowser_click("button[type=submit"]")browser_wait(".success-message", condition="visible")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."
browser_click("#delete-btn")browser_accept_dialog— click OK on theconfirm("Are you sure?")browser_wait(".deleted-notice", condition="present")
Scroll and capture a long page
Ask: "Screenshot the footer of the page."
browser_open+browser_navigatebrowser_scroll("footer")— scroll the footer element into viewbrowser_screenshot("footer")— capture just the footer element
Or scroll by offset to trigger lazy-loaded content:
browser_scroll(by=True, y=1000)— scroll down 1000 pxbrowser_wait(".lazy-section", condition="visible")— wait for lazy contentbrowser_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:
{
"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?"
browser_open+browser_navigate("/shop")devtools_network_all(slow_ms=500, limit=20)— requests over 500 ms, capped at 20 entriesReport the slowest assets with their URLs, types, and durations
Available Tools
Session management
Tool | Description |
| Open Firefox (URL optional, default |
| Quit the browser session |
| Session state, geckodriver version, BiDi status, current viewport size, buffer counts |
| Resize the viewport mid-session (e.g. 390×844 for iPhone 14) |
Navigation
Tool | Description |
| Navigate to a URL (bare hostnames get |
| Go back in history |
| Go forward in history |
| Reload the current page |
Page inspection
Tool | Description |
| Full-page or element PNG screenshot |
| Current page |
| Current URL |
| Raw HTML source (wrapped as untrusted content — see Security) |
| Visible text (whole page or CSS selector; wrapped as untrusted content) |
| Value of an HTML attribute on an element (wrapped as untrusted content) |
| List all elements matching a CSS selector |
Interaction
Tool | Description |
| Click element (CSS selector) |
| Type text into an input field (clears first by default) |
| Select |
| Run JavaScript — returns JSON |
| Wait: |
| Scroll to coords, by offset, or element into view |
| Send |
| Hover mouse over element ( |
| Switch into |
Dialogs & cookies
Tool | Description |
| Accept a JS |
| Dismiss a JS |
| Read all cookies for the current page |
| Inject a cookie (auth tokens, session IDs) |
DevTools (require BiDi — Firefox + geckodriver ≥ 0.34)
Tool | Description |
| Main diagnostic tool — JS errors + console + failed/slow network |
| JavaScript exceptions only (entries carry an |
| Console output (log / warn / error / info / debug; entries carry an |
| Failed resources (4xx, 5xx, DNS errors) |
| All network requests (supports |
| Clear buffered DevTools data (use before navigating) |
| Attach BiDi listeners to a running session |
| Computed CSS properties of an element |
| Bounding box, visibility, attributes, aria, outerHTML |
| CSS custom properties ( |
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): 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 |
| (unset) | Absolute path to geckodriver binary (highest priority) |
|
| Set to |
| (unset) | Path to a custom Firefox executable |
| (unset) | Named Firefox profile — same as |
| (unset) | Profile directory path — same as |
|
| Set to |
geckodriver resolution order
# | Source | Configure via |
1 |
| Absolute path to the binary |
2 | System PATH (default) |
|
3 | webdriver-manager auto-download | Fallback; disable with |
MCP client configuration
Minimal config:
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver"
}
}
}With a named Firefox profile (stays logged in, uses saved passwords):
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver",
"args": ["-P", "work"]
}
}
}With a profile directory and explicit geckodriver path:
{
"mcpServers": {
"webdriver": {
"command": "mcp-server-webdriver",
"args": ["--profile", "/home/user/.mozilla/firefox/abc123.dev"],
"env": {
"GECKODRIVER_PATH": "/usr/bin/geckodriver"
}
}
}
}Running tests
# Unit tests only (no browser required):
pytest tests/ -m "not integration"
# All tests including browser integration:
pytest tests/Related MCP Servers by VitexSoftware
Server | Description |
AbraFlexi accounting/ERP integration — invoices, contacts, products, bank transactions | |
Mastodon integration — timelines, posting, account management, search | |
Semaphore UI integration — manage Ansible, Terraform and other automation workflows |
License
MIT