MCP browser bridge that gives AI clients full access to a live browser environment.
Project description
๐ค The LLM access layer for a fleet of anti-detect browsers.
An MCP server that lets your AI agents launch, drive, and manage many stealth
Chromium browsers โ each with its own identity, proxy, and persistent state.
๐ค What is mithwire-mcp?
mithwire is an anti-detect
browser. mithwire-mcp is the layer that lets an LLM agent operate a whole
fleet of them. ๐
It speaks the Model Context Protocol, so any MCP-capable client (Claude, Cursor, your own agentโฆ) can ask it to spin up isolated browser sessions, navigate and interact with pages, and manage durable browser identities โ without you writing any browser-automation glue. Every session is a brand-new, isolated browser process; the MCP never touches a browser it didn't spawn.
Built for ๐ธ๏ธ autonomous agents, ๐งฒ scraping pipelines, ๐ฅ multi-account workflows, ๐งช E2E prototyping, and production-style browser operations.
๐ก Want the lower-level Python engine instead of an MCP server? That's mithwire.
๐ฌ Demo
โถ๏ธ Watch the demo video
โจ Highlights
๐ง Manage a fleet, not a single browser
- ๐ Launch fresh isolated sessions on demand with
session_startโ each its own browser process. - ๐ฏ One simple choice per session: ephemeral (default) or a persistent managed
profile. No flaky attach/clone paths. - ๐ Full lifecycle control (
session_start,session_list,session_get,session_stop,session_stop_all) with per-session action locking. - ๐ The MCP never attaches to, takes over, or kills a browser it didn't launch.
๐ค Built for autonomous agents
- ๐ฎ Deterministic navigate / query / click / type / wait / evaluate / screenshot flows.
- ๐๏ธ First-class for scraping pipelines, E2E prototyping, regression checks, and interactive debugging.
- ๐ญ Live operational visibility: console output, request metadata, and CDP-level network capture.
๐ค Durable identity & state
- ๐๏ธ Reusable browser state in one place:
profiles/andconfigs/under a single state root. - ๐ช A managed
profilepersists cookies and storage natively across runs โ no separate cookie bookkeeping. - ๐๏ธ Tune launch behavior: browser flags, executable path, headless/sandbox, and first-class proxy support (incl. authenticated HTTP/HTTPS).
๐ต๏ธ Stealth that stays consistent across the fleet
- ๐ฅท Powered by
mithwire: no-WebDriver/no-Selenium Chromium control, with Cloudflare Turnstile solving (browser_solve_cloudflare). - ๐ญ Control the full identity โ IP (authenticated proxy via local relay), location (proxy-aligned timezone), language, and device profile (fingerprint spoofing) โ kept internally consistent across workers and headers.
- ๐ WebRTC leak protection stops the host's real IP from leaking around the proxy.
- ๐ฐ๏ธ Built on direct CDP control for low-level precision and observability.
๐ Quick start
1๏ธโฃ Prerequisites
- ๐ Python
>=3.10(3.14+works with the latestmithwire) - ๐ A Chromium-based browser installed (Chrome, Brave, or Edge)
- ๐ฆ Optional but recommended:
pipxfor isolated CLI installs
2๏ธโฃ Install
Recommended (isolated, with pipx):
pipx install mithwire-mcp
Or into a virtual environment:
python3 -m venv ~/.venvs/mithwire-mcp
source ~/.venvs/mithwire-mcp/bin/activate
pip install mithwire-mcp
To track the latest unreleased changes, install straight from git:
pipx install "git+https://github.com/codeisalifestyle/mithwire-mcp.git"
Verify:
mithwire-mcp --help
3๏ธโฃ Add it to your MCP client
Most MCP-enabled clients accept a config shaped like this:
{
"mcpServers": {
"mithwire-mcp": {
"command": "mithwire-mcp",
"args": ["--transport", "stdio"]
}
}
}
If your client can't find the command, use an absolute path (which mithwire-mcp
on macOS/Linux, where.exe mithwire-mcp on Windows) as the "command". With the
venv install above, that's typically ~/.venvs/mithwire-mcp/bin/mithwire-mcp.
4๏ธโฃ Smoke test from your agent
After reloading your client, ask it to:
- Call
session_startwith default settings. - Call
browser_navigatetohttps://example.com. - Call
browser_snapshot. - Call
session_stop.
โ If those succeed, you're set.
๐งฉ Launching a session
The MCP always spawns a brand-new, isolated browser process. There are no
"modes" to memorize โ session_start has exactly two shapes:
| Goal | Call | What you get |
|---|---|---|
| ๐๏ธ Throwaway browser (default) | {} |
A fresh ephemeral browser with no saved state. Ideal for scraping and E2E. |
| ๐ค Persistent identity | { "profile": "twitter_main" } |
A managed profile whose cookies/storage persist across runs. |
Everything else is an optional flag on top of those two:
| Option | Default | Purpose |
|---|---|---|
headless |
false (headful) |
Run without a visible window (e.g. CI). |
proxy |
none | Route traffic through an upstream proxy (see below). |
fingerprint |
none | Identity overrides โ timezone, locale/languages, geo, user agent, platform, hardware, screen, WebGL (see below). |
webrtc_leak_protection |
auto |
Guard WebRTC against real-IP leaks: auto / filter / disable / off (see below). |
start_url |
none | Navigate here right after launch. |
cookie_file |
none | One-shot injection of cookies from a JSON file at launch. |
sandbox |
true |
Keep Chromium's sandbox on (recommended; --no-sandbox is easily bot-detected). |
preset |
none | Apply a named preset (a shared bundle of launch settings stored under presets/<name>.json). |
proxy_ref |
none | Use a named entry from the proxy registry instead of inlining credentials in proxy. |
๐ Proxy support
proxy accepts several common spellings and normalizes them:
http://host:portorhttp://user:pass@host:port- the provider
scheme:host:port:user:passform socks5://host:port- an object:
{ "server": "http://host:port", "username": "...", "password": "...", "rotation_url": "https://api.provider.com/rotate?token=..." }
๐ rotation_url is optional โ a provider endpoint that rotates the upstream exit IP
when hit. The MCP stores it on the proxy object at launch; call
session_rotate_proxy to trigger a rotation โ it hits the endpoint, waits a short
settle window, re-probes through the proxy to confirm the new egress, and (by
default) re-aligns the browser identity (timezone, locale, languages, geolocation)
to match. Anything you pinned via fingerprint (or session_set_fingerprint)
keeps winning over the proxy-derived default. Rotation URLs frequently embed a
secret token, so the URL is redacted anywhere it appears in session metadata
or logs (userinfo and query stripped to ?***); the literal URL stays in-memory only.
๐ Authenticated HTTP/HTTPS proxies are fully supported. Rather than answering
the proxy challenge per request over CDP (which floods the event loop and stalls
heavy page loads), the MCP starts a small local authenticating relay: Chromium
is pointed at 127.0.0.1, and the relay injects the upstream
Proxy-Authorization header and pipes bytes through to the real proxy. The
browser never sees a 407. Unauthenticated HTTP/HTTPS and SOCKS proxies go
straight to --proxy-server. Authenticated SOCKS is rejected up front
(Chromium's --proxy-server can't carry SOCKS credentials) โ use the provider's
HTTP/HTTPS endpoint instead.
๐ฉบ Pre-launch proxy health check. A session that asks for a proxy is refused
before any browser is spawned if that proxy is unreachable or rejects the
credentials. The MCP issues a single absolute-form GET http://api.ipapi.is/ to
the proxy (with Proxy-Authorization when present) and only proceeds on a clean
2xx with parseable JSON. There is no fallback to the host's direct connection โ
that would silently leak the real IP into login flows and cross-contaminate any
persistent profile. A bad proxy fails fast with an actionable error.
๐งญ Identity defaults aligned to the proxy egress. The same probe doubles as the
egress lookup: when a proxy is set, the session defaults its identity โ
timezone, locale, languages, Accept-Language, and geolocation โ to the proxy's
egress IP so the two never disagree. Anything explicitly set in fingerprint={...}
or in the profile's launch_options wins. SOCKS proxies get a TCP-only liveness
check, with timezone alignment falling back to the in-browser ipapi.is lookup. The
detected egress (ip, timezone, city, country, country_code) is recorded
in session metadata under proxy_exit.
๐ญ Fingerprint / identity spoofing
Pass a fingerprint object to session_start (or apply one to a live session
with session_set_fingerprint) to control the identity the browser presents. All
fields are optional; anything unset is left untouched:
- ๐
timezone_id,locale,languages,accept_language - ๐
latitude,longitude,geo_accuracy - ๐ฅ๏ธ
user_agent,platform,hardware_concurrency,device_memory(GB) - ๐
screenโwidth,height,device_scale_factor,mobile,max_touch_points - ๐จ
webgl_vendor,webgl_renderer
Overrides are applied at the engine level via CDP Emulation.* wherever
Chromium supports it, so they propagate to Web Workers and HTTP request headers โ
not just the main document โ keeping every signal internally consistent (a
mismatched override is worse than none). The handful of properties with no CDP
equivalent (navigator.deviceMemory, and the WebGL strings when requested) fall
back to injected JS.
โ ๏ธ Two rules matter: keep overrides same-OS-family (don't claim a Windows UA on a macOS host), and if you spoof geo/timezone, back it with a matching proxy so the egress IP agrees.
๐ WebRTC leak protection
WebRTC can open a STUN connection that reveals the host's real local and public
IPs directly, bypassing the proxy entirely (UDP isn't proxied) โ the single
biggest de-anonymization leak for a proxied browser. The
webrtc_leak_protection option controls the guard:
| Mode | Behavior |
|---|---|
auto (default) |
Filter leaky ICE candidates when a proxy is set; otherwise leave WebRTC intact. |
filter |
Always drop public, non-egress ICE candidates and scrub SDP. |
disable |
Remove RTCPeerConnection entirely (no WebRTC at all). |
off |
No protection (real IP can leak). |
๐ฌ Verifying stealth
scripts/verify_mcp.py launches a real session (the same BridgeBrowser path the
MCP uses) against public bot-detection services and asserts the critical signals
are clean โ useful as a regression check after touching launch/stealth/proxy code:
python3 scripts/verify_mcp.py --headless # deviceinfo + fingerprint
python3 scripts/verify_mcp.py --site deviceinfo
python3 scripts/verify_mcp.py --proxy "http://user:pass@host:port" # also checks TZ alignment
๐ช Cookies
A managed profile stores its cookies in Chromium's native cookie store, so they
persist automatically โ nothing extra to manage. The only separate cookie
operations are injection (cookie_file at launch, or browser_cookies_set at
runtime) and export (browser_cookies_get / browser_cookies_save).
๐๏ธ cookie_file and browser_cookies_save's output_path resolve relative
paths against the managed ~/.mithwire-mcp/cookies/ inbox: a profile can carry
"cookie_file": "twitter.json" instead of an absolute path that only makes sense
on one machine, and a browser_cookies_save with output_path: "exit.json" lands
in the same place ready to be re-injected next session. Absolute and
~-prefixed paths are returned unchanged for full back-compat.
๐๏ธ Centralized browser state store
mithwire-mcp keeps reusable browser state in one place:
- ๐ Default root:
~/.mithwire-mcp - ๐ฑ Override with env var:
MITHWIRE_MCP_HOME=/custom/path - ๐ Override per server run:
mithwire-mcp --state-root /custom/path
Layout under that root:
~/.mithwire-mcp/
โโโ profiles/<name>/ # Chromium user-data dir + profile.json metadata
โโโ presets/<name>.json # opt-in shared launch recipes (e.g. mac-us)
โโโ proxies/<name>.json # first-class proxy registry (creds + rotation_url)
โโโ cookies/ # one-shot cookie injection / export files
session_start resolves launch settings in four layers, lowest precedence first:
- Built-in defaults
- Effective preset values (the session's
presetarg if given, else the profile'spresetfield โ never both at once) - The profile's
launch_options(per-profile overrides) - Explicit
session_startarguments
After merging, proxy_ref (if set anywhere in the chain) is expanded against
the proxy registry โ unless a literal proxy was supplied at the same or
higher layer, in which case the literal wins.
๐ฐ๏ธ Proxy registry
Several profiles can share one proxy (most common for residential / rotating endpoints). Store credentials once, reference them by name:
// proxies/oxy-us.json (written by session_proxy_set)
{
"name": "oxy-us",
"scheme": "http",
"host": "us-pr.oxylabs.io",
"port": 7777,
"username": "...",
"password": "...",
"rotation_url": "https://api.example.com/rotate?token=...",
"tags": ["residential", "us"]
}
// profiles/alice/profile.json
{
"description": "Alice",
"account_aliases": ["alice@example.com"],
"preset": "mac-us",
"launch_options": {
"proxy_ref": "oxy-us",
"fingerprint": { "timezone_id": "America/New_York" }
}
}
The pre-launch proxy preflight (reachability + credentials probe) still runs
on the resolved proxy; a failed probe refuses the session, regardless of
whether the proxy came in as a literal or via proxy_ref. No half-launched
browser, no silent fallback to the host's direct connection.
๐ Migration from older layouts
On startup the state store transparently fixes up the previous schema:
~/.mithwire-mcp/configs/โ~/.mithwire-mcp/presets/(file moves, no silent merge if the destination already exists).profile.json: the oldlaunch_configandlaunch_overrideskeys are rewritten topresetandlaunch_optionsrespectively, in place. Atomic and idempotent โ safe to re-run on every server start.
For an explicit report (what got renamed, which profiles were rewritten,
whether a legacy presets/default.json is hanging around no-op, and which
inlined proxies could be deduplicated into the registry) run the dedicated
subcommand:
# Dry-run: simulate against a temp copy, print the report, change nothing.
mithwire-mcp migrate-state --dry-run --extract-proxies
# Apply for real, auto-name extracted proxy entries from host:port.
mithwire-mcp migrate-state --extract-proxies --auto-name
# Or omit --extract-proxies to leave inlined proxies alone for now โ
# they keep working; the report just surfaces them as candidates.
mithwire-mcp migrate-state
--extract-proxies collapses inlinings that share the same upstream
credentials into a single proxies/<name>.json and rewrites every call-site
to reference it by name. Idempotent: re-running after a successful migration
is a no-op.
Note on
default.json. In the old layout,configs/default.jsonauto-applied as a baseline for every session. After migration, the renamedpresets/default.jsonis not applied implicitly โ link it from a profile via"preset": "default", or move its values inline. The migration command logs this once so the behavioural difference is never silent.
๐งฐ Tools exposed
๐ Session lifecycle
session_start, session_list, session_get, session_state_paths,
session_profile_list, session_profile_get, session_profile_set,
session_profile_delete, session_preset_list, session_preset_get,
session_preset_set, session_preset_delete, session_proxy_list,
session_proxy_get, session_proxy_set, session_proxy_delete,
session_set_fingerprint, session_rotate_proxy, session_set_policy,
session_get_policy, session_set_download_dir, session_trace_start,
session_trace_stop, session_trace_get, session_trace_export,
session_trace_replay, session_stop, session_stop_all
๐ฎ Browser actions
browser_url, browser_navigate, browser_back, browser_forward,
browser_reload, browser_tab_list, browser_tab_new, browser_tab_switch,
browser_tab_close, browser_tab_current, browser_snapshot, browser_query,
browser_click, browser_type, browser_handle_dialog, browser_set_file_input,
browser_scroll, browser_wait, browser_wait_for_selector,
browser_wait_for_url, browser_wait_for_text, browser_wait_for_function,
browser_wait_for_network_idle, browser_html, browser_console_messages,
browser_network_requests, browser_network_capture_start,
browser_network_capture_get, browser_network_capture_stop,
browser_network_capture_status, browser_downloads, browser_cookies_get,
browser_cookies_set, browser_cookies_save, browser_cookies_clear,
browser_storage_get, browser_storage_set, browser_storage_clear,
browser_take_screenshot, browser_evaluate, browser_solve_cloudflare
๐ ๏ธ Development
mithwire-mcp is a standalone repo that depends on the published
mithwire engine. It uses
uv:
git clone https://github.com/codeisalifestyle/mithwire-mcp.git
cd mithwire-mcp
uv sync # creates .venv and installs the package + dev deps
uv run pytest -q
uv run mithwire-mcp --transport stdio
Point your client at the dev binary with an absolute path:
{
"mcpServers": {
"mithwire-mcp": {
"command": "/absolute/path/to/mithwire-mcp/.venv/bin/mithwire-mcp",
"args": ["--transport", "stdio"]
}
}
}
To hack on the engine and the MCP together, install the engine editable into this
venv: uv pip install -e /path/to/mithwire.
๐ Architecture notes live in docs/architecture.md.
๐ฉน Troubleshooting
command not found: mithwire-mcpโ runpipx ensurepath, restart your terminal and client, then retry; or use the absolute path fromwhich mithwire-mcp.- Python version mismatch โ use Python
>=3.10and reinstall/upgrade the package in your MCP environment. - Browser fails to launch in restricted environments โ try
session_startwithsandbox=false. - Client shows no tools โ confirm the JSON is valid, transport is
stdio, and fully restart the client after editing the config.
โ ๏ธ Safety notes
- Use this only on sites and accounts you're authorized to access.
- Respect website Terms of Service and local regulations.
- Be cautious with high-frequency automation.
๐ License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mithwire_mcp-0.2.2.tar.gz.
File metadata
- Download URL: mithwire_mcp-0.2.2.tar.gz
- Upload date:
- Size: 140.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c92f850542ac076cb86c3759dfbb5c62acf9f295f77cae0c1e46d7abd4ae4fcc
|
|
| MD5 |
78076ff4b86ee9f6e3219e069bbc98e7
|
|
| BLAKE2b-256 |
60fc180ac413b3c893904f386fdb73d06c7579fd0a27525faa7b0b940330be0b
|
Provenance
The following attestation bundles were made for mithwire_mcp-0.2.2.tar.gz:
Publisher:
release.yml on codeisalifestyle/mithwire-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mithwire_mcp-0.2.2.tar.gz -
Subject digest:
c92f850542ac076cb86c3759dfbb5c62acf9f295f77cae0c1e46d7abd4ae4fcc - Sigstore transparency entry: 2168033931
- Sigstore integration time:
-
Permalink:
codeisalifestyle/mithwire-mcp@8e7cfcee834ad20817b4335d15d4e77b46e05b48 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/codeisalifestyle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8e7cfcee834ad20817b4335d15d4e77b46e05b48 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mithwire_mcp-0.2.2-py3-none-any.whl.
File metadata
- Download URL: mithwire_mcp-0.2.2-py3-none-any.whl
- Upload date:
- Size: 98.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f5c0c80b63105be986d518f734b691b78b51dfce5172041f752bfda0da7412b
|
|
| MD5 |
b39c25ba7e3906a4f3776c9c55be96be
|
|
| BLAKE2b-256 |
555e09aa4d306d116cec5b7658e83459507d1f0d0583b76e068cf0cf61386d37
|
Provenance
The following attestation bundles were made for mithwire_mcp-0.2.2-py3-none-any.whl:
Publisher:
release.yml on codeisalifestyle/mithwire-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mithwire_mcp-0.2.2-py3-none-any.whl -
Subject digest:
0f5c0c80b63105be986d518f734b691b78b51dfce5172041f752bfda0da7412b - Sigstore transparency entry: 2168033939
- Sigstore integration time:
-
Permalink:
codeisalifestyle/mithwire-mcp@8e7cfcee834ad20817b4335d15d4e77b46e05b48 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/codeisalifestyle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8e7cfcee834ad20817b4335d15d4e77b46e05b48 -
Trigger Event:
push
-
Statement type: