Stealth browser automation framework that talks directly to Chrome via CDP (no webdriver, no chromedriver). A maintained fork of nodriver.
Project description
🥷 An anti-detect browser for Python.
Drive real Chrome straight over the DevTools Protocol — no WebDriver, no Selenium, no chromedriver — with stealth and Cloudflare Turnstile solving built in.
🤔 What is mithwire?
mithwire is an anti-detect browser automation framework. It launches a normal Chromium-based browser (Chrome, Brave, Edge…) and controls it by talking directly to the Chrome DevTools Protocol (CDP).
There's no automation driver bolted on the side — so the tell-tale signals anti-bot systems look for to spot WebDriver/Selenium simply aren't there. You get a browser that behaves like a real one, a short and pleasant async API, and the practical stealth fixes you actually need against modern anti-bot stacks.
✨ Features
🥷 Anti-detect by design — speaks raw CDP, so there's no navigator.webdriver, no chromedriver binary, and no Selenium surface to fingerprint
🌐 Real Chromium browsers — works with Chrome, Chromium, Brave, and Edge
👁️ Headful or headless — run with a visible window, or invisibly on a server (pair with Xvfb when you need a real display)
🎭 Identity & fingerprint control — full CDP Emulation access to shape what the browser presents: timezone, locale, geolocation, user agent, device metrics, and more
🌍 Proxy support — route traffic through upstream HTTP/SOCKS proxies
🛡️ Cloudflare Turnstile bypass — one call (tab.verify_cf()) solves the checkbox challenge, light and dark mode
🔎 Smart DOM access — find elements by text, CSS selector, or XPath, including inside iframes — lookups double as wait conditions
📡 Full network & event access — the entire CDP surface is yours: requests, responses, events, interception
🍪 Stateful when you want it — cookies, localStorage, multi-tab & multi-window, screenshots, and external-debugger attach
⚡ Tiny, async API — up and running in ~2 lines, with best-practice defaults and automatic profile cleanup
🤖 mithwire-mcp — give your AI agents a browser fleet
One mithwire browser is great for a script. But what about an AI agent that needs to run dozens of them? That's mithwire-mcp — a Model Context Protocol server that turns mithwire into a tool your LLM can drive directly.
- 🧠 Agents launch & control their own browsers —
session_start, navigate, click, type, screenshot, evaluate — all as MCP tools - 🚀 Fleet management — spin up many isolated sessions at once, each its own browser process, with full lifecycle control
- 👤 Durable identities — persistent profiles with their own cookies, proxies, and fingerprints that survive across runs
- 🕵️ Consistent stealth at scale — proxy-aligned timezone/locale/geo, fingerprint spoofing, and WebRTC leak protection kept in sync per worker
➡️ Get started with mithwire-mcp →
📦 Install
pip install mithwire
You'll need a Chromium-based browser (Chrome/Brave/Edge) installed, ideally in the
default location. On a headless server, run under Xvfb
or use headless mode. To upgrade:
pip install -U mithwire
🚀 Quick start
import mithwire as uc
async def main():
browser = await uc.start()
page = await browser.get("https://www.nowsecure.nl")
await page.save_screenshot()
uc.loop().run_until_complete(main())
💡 Use
uc.loop().run_until_complete(...)instead ofasyncio.run(...)for reliable event-loop handling.
🧭 Usage
⚙️ Custom launch options
from mithwire import start
browser = await start(
headless=False,
user_data_dir="/path/to/profile", # pass one and it won't be auto-cleaned on exit
browser_executable_path="/path/to/some/other/browser",
browser_args=["--some-flag=true"],
lang="en-US",
)
tab = await browser.get("https://example.com")
Or configure via a Config object:
from mithwire import Config
config = Config()
config.headless = False
config.user_data_dir = "/path/to/profile"
config.browser_args = ["--some-flag=true"]
🔎 Finding things on the page
# by text — returns the closest match by text length, not the first hit
accept = await tab.find("accept all", best_match=True)
await accept.click()
# by CSS selector (retries until found or <timeout>, so it doubles as a wait)
email = await tab.select("input[type=email]")
imgs = await tab.select_all("a[href] > div > img")
# by XPath
node = await tab.xpath("//button[contains(., 'Next')]", timeout=2.5)
🛡️ Solving Cloudflare Turnstile
page = await browser.get("https://site-behind-turnstile.example")
await page.verify_cf(max_retries=3, timeout=20, retry_interval=2)
Bundled with light- and dark-mode widget templates and HiDPI-correct coordinates.
Requires opencv-python (pip install opencv-python).
🧰 Handy tab helpers
| Method | Does |
|---|---|
tab.get_content() |
current page HTML |
tab.save_screenshot() |
screenshot to a temp file |
tab.scroll_down(n) / tab.scroll_up(n) |
scroll the page |
tab.get_local_storage() / tab.set_local_storage(dict) |
read/write localStorage |
tab.add_handler(event, cb) |
subscribe to CDP events (cb(event) or cb(event, tab)) |
tab.bypass_insecure_connection_warning() |
click through invalid-cert warnings |
tab.open_external_debugger() |
inspect a tab without breaking your connection |
A fuller, runnable script (automating account creation end to end) lives in the
example/ folder. 📂
⚖️ How it compares
Most "stealth" tools either drive the browser through WebDriver/Selenium (which leaks an obvious automation surface) or patch over it with injected JavaScript (which is itself detectable). mithwire skips both: it speaks the browser's own debug protocol directly, so there's no driver to fingerprint and no injected shim to catch — while still giving you the full power of CDP for low-level control.
🙏 Credits & license
mithwire is a maintained fork of nodriver
by UltrafunkAmsterdam, itself the successor to undetected-chromedriver. It's
distributed under the GNU AGPL-3.0, the same license as upstream. Original
copyright and license are preserved in LICENSE.txt; attribution
details are in NOTICE. mithwire is not affiliated with or endorsed by
the original author.
This fork addresses real-world limitations hit when running against modern anti-bot systems; fixes land as they're discovered during active use.
⚠️ Use responsibly. Only automate sites and accounts you're authorized to use, respect Terms of Service and local law, and avoid abusive request rates.
Project details
Release history Release notifications | RSS feed
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-0.50.8.tar.gz.
File metadata
- Download URL: mithwire-0.50.8.tar.gz
- Upload date:
- Size: 393.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34d82374b857964d992e5ba904898a8ee0a6facdc34dfc9b2be83e0745926fb2
|
|
| MD5 |
7e4618d3a9474d439fc0f55efd12359c
|
|
| BLAKE2b-256 |
bc16d0d9559483abeb2ea89a447d2da585e8742270fd3b2c055dec4fa86545ca
|
Provenance
The following attestation bundles were made for mithwire-0.50.8.tar.gz:
Publisher:
release.yml on codeisalifestyle/mithwire
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mithwire-0.50.8.tar.gz -
Subject digest:
34d82374b857964d992e5ba904898a8ee0a6facdc34dfc9b2be83e0745926fb2 - Sigstore transparency entry: 2168032982
- Sigstore integration time:
-
Permalink:
codeisalifestyle/mithwire@f57fbf5785758dba6b8fdc65c2cd84a7c600a900 -
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@f57fbf5785758dba6b8fdc65c2cd84a7c600a900 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mithwire-0.50.8-py3-none-any.whl.
File metadata
- Download URL: mithwire-0.50.8-py3-none-any.whl
- Upload date:
- Size: 415.3 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 |
df346fbcf90e1164609052bb23ba35573385a9adc9e98325319c78107b7a93f7
|
|
| MD5 |
8488c25be0ba0fcffb069531f1346055
|
|
| BLAKE2b-256 |
6d5e62082514e9c0aa720f6d342a3e277775595c6082f1cc8d1694983e2940ca
|
Provenance
The following attestation bundles were made for mithwire-0.50.8-py3-none-any.whl:
Publisher:
release.yml on codeisalifestyle/mithwire
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mithwire-0.50.8-py3-none-any.whl -
Subject digest:
df346fbcf90e1164609052bb23ba35573385a9adc9e98325319c78107b7a93f7 - Sigstore transparency entry: 2168032999
- Sigstore integration time:
-
Permalink:
codeisalifestyle/mithwire@f57fbf5785758dba6b8fdc65c2cd84a7c600a900 -
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@f57fbf5785758dba6b8fdc65c2cd84a7c600a900 -
Trigger Event:
push
-
Statement type: