You ask your agent to read a product page and it summarizes Cloudflare's "Just a moment..." screen instead. Or it opens a profile and reports back a login modal.
Stealth Browser MCP is built for that moment: stealth browser automation for MCP-compatible AI agents.
With it, Claude Code, Cursor, or Claude Desktop drives a real Chrome-family browser through Cloudflare challenges, anti-bot checks, and login walls.
This guide sets it up, gives you the prompts that get through a challenge and a login wall, and shows where it still gets caught.
If you've used Playwright MCP with Claude and Cursor, the setup will feel familiar; the browser underneath is what changes.
What is Stealth Browser MCP?
Stealth Browser MCP is an open-source MCP server that gives AI agents stealth browser automation. It launches real Chrome, Chromium, or Edge instances through nodriver, the Chrome DevTools Protocol, and FastMCP, so Claude Code or Cursor can get through Cloudflare challenges, anti-bot checks, and login walls. Install it from GitHub and register it as a stdio server.
The project lives at github.com/vibheksoni/stealth-browser-mcp. It exposes 98 tools in 11 sections, from navigation and clicks to network hooks and element cloning.
It's MIT licensed and had about 2.2k stars and 296 forks when I checked on October 17, 2026.
Version 0.2.5 shipped on February 10, 2026, but master moves faster than the tags.
The last commit I saw was dated October 12, 2026. The newest tool, press_key, still sits under "Unreleased" in the changelog.
It runs on nodriver, the successor to undetected-chromedriver. Nodriver talks to Chrome over CDP without a WebDriver binary, so the usual navigator.webdriver flag never gets set.
FastMCP handles the MCP side: it turns each Python function into a tool your client can call. Our nodriver scraping tutorial covers the browser library on its own.
Three different projects share the name
Search for "stealth browser mcp" and you'll find directory pages for at least three projects. Two of them have nothing to do with this one, and most directories don't say which they mean.
| Project | Engine | Language | Tools | What it does |
|---|---|---|---|---|
| vibheksoni/stealth-browser-mcp | nodriver + CDP, real Chrome/Edge | Python 3.10+ | 98 (21 in --minimal) |
Full browser control, network hooks, element cloning |
| newbeb/stealth-browser-mcp | Playwright + puppeteer-extra stealth plugin | TypeScript (Bun) | 1 (screenshot) |
Takes screenshots of pages |
| brian-ln copy of the newbeb server | Same as newbeb | TypeScript (Bun) | 1 | Same screenshot tool |
If a page tells you to run bun install, it's describing the Playwright one. This article is about the vibheksoni server only.
Stealth Browser MCP vs Playwright MCP
Pick the stealth server when the target has bot protection; Playwright MCP is the better default for everything else.
Playwright MCP launches a browser through Playwright's own protocol layer and drives it through the accessibility tree. Sites running Cloudflare or similar checks flag that setup often enough that you'll notice.
| Stealth Browser MCP | Playwright MCP | |
|---|---|---|
| Browser driver | nodriver over raw CDP | Playwright |
| Tool count | 21 to 98, by section | About 20 |
| Network interception | Read bodies, rewrite, block, fulfill | Basic |
| Element cloning | CSS, DOM, events, animations, assets | No |
| Install | Clone, venv, pip install |
One npx command |
| Maturity | Community project, 65 commits | Microsoft-backed |
The install line matters more than it looks. Playwright MCP is one command; this one is a Python project you keep updated yourself with git pull.
How does Stealth Browser MCP avoid bot detection?
Stealth Browser MCP avoids bot detection by launching the Chrome or Edge already installed on your machine and driving it over the Chrome DevTools Protocol (CDP) without WebDriver. It also keeps the launch flags and input events of a normal user session. The site talks to a genuine browser binary with its own network stack, GPU access and event pipeline, so fingerprint checks read real values instead of patched ones.
Anti-bot systems such as Cloudflare check several layers at once. The server handles most of them and leaves IP reputation entirely to you.
Which detection layers the server covers
| Layer the site checks | What gives automation away | What the server does |
|---|---|---|
| IP reputation | Datacenter ASN, IP seen on many accounts | Nothing by default; bring a proxy (Step 7) |
| TLS and HTTP/2 fingerprint | A Python or Go client's handshake | Requests come from Chrome's own network stack |
| Automation flags | navigator.webdriver, --enable-automation |
No chromedriver; the benchmark checks the real command line |
| CDP side effects | Runtime.enable traces |
--xpool-safe drops the tools that trigger it |
| Input events | Untrusted or out-of-order events | Clicks and keys go through CDP Input events |
| Rendering | Software WebGL renderer | Real GPU on a desktop; leaks on GPU-less Linux |
IP and TLS are judged when the connection opens, before any page JavaScript runs. A clean browser fingerprint can't rescue a request coming from a flagged datacenter IP, which is why the proxy step matters as much as the browser itself.
Why the TLS layer comes free
Python HTTP libraries and Go clients present a TLS and HTTP/2 handshake that differs from Chrome's, and fingerprinting services compare that handshake with the user agent the client claims. Every request here leaves through Chrome's own network stack, so the handshake matches the browser by default. Tools that skip the browser have to spoof it instead: the separate mcp-stealth-chrome server, for example, adds a curl_cffi HTTP mode for exactly that purpose.
Launch flags and the debugging port
Chromedriver-based tools give themselves away through navigator.webdriver and the --enable-automation flag. This server never starts chromedriver, and its benchmark inspects the actual Chrome command line to confirm neither appears.
One detail comes from the source, not the README. In src/browser_launcher.py, the server avoids --remote-debugging-port=0, because Chrome treats that value as automation and sets navigator.webdriver. It picks a free port instead, so CDP stays available without tripping the flag.
CDP leaves its own traces. Calling Runtime.enable produces side effects that some anti-bot scripts probe for. Starting the server with --xpool-safe drops the tools that need it, trading a smaller toolset for a quieter session on sites that look for those traces.
Input events, and which tools need a local client
Clicks and keystrokes go through CDP Input events, which the page receives as trusted events. The benchmark's "input fidelity" target checks that click_element and type_text produce trusted pointer, mouse, and key events in a real order. Sites that score mouse movement look at exactly that, and events dispatched from page JavaScript fail the check because they arrive marked as untrusted.
Two of the server's 98 tools only work for trusted local clients: arbitrary script execution inside the page, and synthesized key events. The HTTP transport has its own separate protection. Running the server locally over stdio changes nothing.
If you connect from a remote or hosted client, expect those two tools to be unavailable, which rules out fixes that depend on them, such as injecting JavaScript to remove an overlay or sending raw key presses to submit a form.
Headed or headless
headless defaults to False. Headless runs scored the same as headed runs in the latest benchmark, but a visible window is the safer start on a site you haven't tested, and it lets you watch where the agent gets stuck.
What stays your job
- IP reputation. A datacenter IP, or one shared across many accounts, gets flagged however clean the browser is. Add a proxy (Step 7).
- Rendering on servers. On a desktop the browser uses the real GPU. On Linux machines without one, Chrome falls back to a software WebGL renderer that fingerprinting scripts can read.
- Interactive challenges. The server gets a real browser through passive checks, but it has no solver for image puzzles or interactive Turnstile challenges. If you need one, mcp-stealth-chrome is a nodriver and FastMCP alternative that claims a one-line Turnstile bypass and an AI-vision reCAPTCHA solver, and it installs with a single uvx command.
Prerequisites
You need four things before Step 1:
- Python 3.10 or newer (the project bumped this from 3.8 in 0.2.5 because FastMCP needs it)
- Chrome, Chromium, or Microsoft Edge installed in a standard location
- An MCP client: Claude Code, Claude Desktop, Cursor, or anything else that speaks stdio MCP
- Git, because
requirements.txtpulls thepy2jspackage straight from GitHub at a pinned commit
The pinned versions on master right now are nodriver 0.47.0 and fastmcp 2.11.2. Don't upgrade either one on your own; the weekly benchmark runs against those exact pins.
Step 1: Install the vibheksoni server from source
Clone the repository, create a virtual environment inside it, and install the pinned requirements.
These are the README's commands, condensed for macOS and Linux:
git clone https://github.com/vibheksoni/stealth-browser-mcp.git
cd stealth-browser-mcp
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python src/server.py --list-sections # sanity check: prints the 11 sections
Keep the venv inside the repo folder. Your MCP client config will point at venv/bin/python by absolute path, so moving it later breaks the server silently.
Step 2: Register the server in Claude Code, Claude Desktop, or Cursor
Register the server as a stdio process whose command is the venv's Python and whose argument is src/server.py.
For Claude Code, one command does it. Replace both paths with your real clone path:
claude mcp add-json stealth-browser-mcp '{
"type": "stdio",
"command": "/home/you/stealth-browser-mcp/venv/bin/python",
"args": ["/home/you/stealth-browser-mcp/src/server.py"]
}'
Claude Desktop and Cursor read the same shape from a JSON file. On macOS, Claude Desktop's file is ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows it's %APPDATA%\Claude\claude_desktop_config.json.
{
"mcpServers": {
"stealth-browser-mcp": {
"command": "/home/you/stealth-browser-mcp/venv/bin/python",
"args": ["/home/you/stealth-browser-mcp/src/server.py", "--minimal"],
"env": { "BROWSER_IDLE_TIMEOUT": "900" }
}
}
}
I added --minimal and a 15-minute idle timeout here on purpose. Step 3 explains both.
On Windows, every backslash in those paths needs doubling. A single \ in the JSON is the most common reason the server shows up as "failed" in the client.
Step 3: Cut the tool list to what your agent needs
Start with --minimal, which loads 21 tools instead of 98, and add sections back only when a task needs them.
Every tool's name and description lands in the model's context on every turn.
With 98 of them, the agent spends tokens reading about pseudo-element expansion while you're asking it to click a login button.
Minimal mode keeps the two sections you'll use daily: browser-management (8 tools) and element-interaction (13). That covers spawning, navigation, clicks, typing, scrolling, page content, and screenshots.
When you need more, run the full server and disable what you don't want:
# Network work: keep traffic inspection and hooks, drop cloning and raw CDP
python src/server.py \
--disable-cdp-functions \
--disable-progressive-cloning \
--disable-file-extraction \
--disable-element-extraction
The same flags go into the args array of your client config. Each flag removes one section, so you can build any combination of the 11.
What --xpool-safe does
The --xpool-safe flag drops the 14 cdp-functions tools and leaves 84.
Those tools call the CDP Runtime.enable command. Some anti-bot scripts can spot that command's side effects from inside the page.
Runtime is the CDP domain an automation client enables to evaluate JavaScript, so detectors probe for traces of it.
If a site passes in minimal mode but fails in full mode, try --xpool-safe before anything else.
Step 4: Run your first stealth session
Ask your agent in plain English; it will chain spawn_browser, navigate, a state check, and take_screenshot on its own.
Restart the client after editing the config, then try a prompt like this one:
Use stealth-browser-mcp to open https://example.com in a visible browser,
take a screenshot, read the page title, then close the instance.
Under the hood, spawn_browser returns an instance_id. Every follow-up call carries that ID, which is how one agent can juggle several browsers at once.
The repo ships an agent skill at skills/stealth-browser-mcp with the recommended call order. If your agent keeps forgetting to verify state after a click, symlink that folder into your client's skills directory.
One spawn_browser argument is worth setting from the start: idle_timeout_seconds overrides the global 600-second reaper for that instance.
Close instances when you're done. Each one is a full Chrome process with its own temp profile, and the reaper only catches them after the timeout.
Step 5: Get your agent through a Cloudflare challenge
Open the page in a headed browser, let the challenge clear on its own, and have the agent confirm it landed on the real page before it reads anything.
This prompt uses nowsecure.nl, the Cloudflare test page the project's benchmark runs against:
Spawn a visible stealth browser and open https://nowsecure.nl.
If the page title is "Just a moment...", wait up to 20 seconds for it to change.
Take a screenshot, then list the cookies for the site and tell me
whether a cf_clearance cookie was set.
The cookie check needs the cookies-storage section, which --minimal leaves out. Add it back, or drop that last line.
Agents tend to read the page the moment navigate returns. On a challenge page that means summarizing Cloudflare's interstitial and calling it done.
On real targets, tell the agent which element proves it's through: "wait for .product-grid" beats "wait 10 seconds".
wait_for_element takes a selector and a timeout in milliseconds, so it returns as soon as the page is ready.
The cf_clearance cookie only stays valid from the same IP and browser that earned it. Keep the same instance, or the same sticky proxy session, for every request that depends on it.
Our guide to the cf_clearance cookie covers how long it lasts and what invalidates it.
An interactive Turnstile checkbox or an image puzzle is a different story. The server has no solver, so the run stops there unless you click it yourself in the headed window.
Step 6: Read pages behind a login wall
A login wall is either a modal over content that already loaded or a real sign-in. Read around the first; log into the second once and save the session.
Content behind an overlay
Many sites send the full page and then cover it with a "sign in to continue" modal. The data is already in the DOM; the browser isn't showing it to you.
The maintainer's manual test from February 10, 2026, did this on an X.com profile. The full profile loaded behind the login modal.
Removing the overlay through DOM manipulation made the data readable.
Open https://forum.example.com/thread/123 in the stealth browser.
If a sign-in modal covers the page, don't log in. Read the content behind it
with get_page_content, or remove the modal with execute_script,
then extract every post's author, date, and text.
Both tools are in --minimal. X changes its markup often, so treat that test as a pattern to copy, not a guarantee for any one site.
Real logins: keep a profile
When the content only loads after you sign in, log in once and reuse the session.
Pass user_data_dir to spawn_browser, and Chrome keeps cookies, local storage, and logins in that folder between runs.
Without it, every instance starts from a fresh uc_* temp profile. You solve the same challenge and log in again on every run.
Do the first login by hand in the headed window, especially when a 2FA prompt appears. Close the instance, then point the next spawn_browser call at the same folder.
If the agent types the credentials itself, type_text sends human-like keystrokes. Use press_key for Enter on React forms, which ignore the synthetic Enter that type_text sends.
Use one profile folder per account. Chrome locks a profile while it's open, so a second instance pointed at the same folder won't start cleanly.
Step 7: Route the browser through an authenticated proxy
Pass a proxy URL with credentials to spawn_browser's proxy argument, and the server starts a local forwarder that handles the authentication for Chrome.
This is the part the README skips. Chrome's --proxy-server flag has no slot for a username and password, so the usual nodriver workaround is a throwaway extension that answers the auth prompt.
The server solves it differently. I read through src/proxy_forwarder.py: when your proxy URL has credentials, it opens a listener on a random 127.0.0.1 port and points Chrome at that.
Spawn a stealth browser with:
proxy = "http://user:p%[email protected]:7000"
timezone_id = "Europe/Berlin"
block_resources = ["image", "font", "media"]
Then open https://httpbin.org/ip and tell me the IP it reports.
The forwarder injects the Proxy-Authorization header upstream and strips it from what Chrome sends. Percent-encode reserved characters in the password (@ becomes %40); the parser decodes them before use.
Set timezone_id to match the proxy's exit country. A German residential IP paired with an America/New_York clock is the kind of mismatch fingerprinting scripts check first.
The proxy type matters as much as the browser. A datacenter IP with a perfect fingerprint still gets challenged on many Cloudflare-protected sites, because the reputation check happens before any JavaScript runs.
That's where residential or ISP proxies come in, from Roundproxies or whichever provider you already pay.
Use sticky sessions. A challenge cookie earned on one IP rarely survives a hop to another.
block_resources is the cheap win. Skipping images and fonts cuts bandwidth per page, which adds up when you pay per gigabyte.
Leave stylesheets alone on challenge pages so the page renders the way a person would see it.
Step 8: Inspect and rewrite network traffic with dynamic hooks
Use the network-debugging tools to find the JSON calls behind a page, then use a dynamic hook to block, redirect, modify, or fake those requests as they happen.
Most dynamic sites load their data from an internal API. Finding it in DevTools takes a few minutes of clicking. Here the agent does it:
Open https://shop.example.com/category/shoes and scroll to the bottom.
List the network requests, find XHR or fetch calls that return JSON with
product names and prices, and show me the response body of the largest one.
The agent calls list_network_requests, filters by resource type, then reads one body with get_response_content. Large results get written to a file instead of flooding the chat.
Once you know the endpoint, you rarely need the browser for it. Replay it with a TLS-matched HTTP client like curl_cffi and keep the browser for cookies and challenges.
Write a hook function
A hook is a short Python function named process_request that receives the request and returns a HookAction.
Suppose that shop's listing API returns 24 items per page and accepts a bigger size. This hook rewrites the URL before it leaves the browser:
def process_request(request):
url = request["url"]
# The page asks for 24 items; ask the API for 100 instead
if "pageSize=24" in url:
return HookAction(
action="redirect",
url=url.replace("pageSize=24", "pageSize=100"),
)
# Anything else passes through untouched
return HookAction(action="continue")
You hand that code to create_dynamic_hook with a requirements dict such as {"url_pattern": "*/api/products*"}.
Run validate_hook_function first. Hooks execute in a restricted Python environment, so imports you'd normally reach for may not exist.
The five actions are continue, block, redirect, modify (headers, method, body), and fulfill (a fake response).
For one-liners like blocking an ad domain, create_simple_dynamic_hook takes a URL pattern and an action name. No code needed.
Step 9: Drive the server from a Python script
Connect to the server with the FastMCP client and call its tools directly, with no LLM in the loop.
This is the step I'd point most scraping readers to. Once the agent has worked out a flow, paying for tokens on every repeat run makes no sense.
import asyncio
from fastmcp import Client
SERVER = "/home/you/stealth-browser-mcp/src/server.py"
async def main():
async with Client(SERVER) as client: # starts server.py over stdio
spawn = await client.call_tool("spawn_browser", {"headless": False})
iid = spawn.data["instance_id"]
await client.call_tool("navigate", {"instance_id": iid, "url": "https://example.com"})
page = await client.call_tool("get_page_content", {"instance_id": iid})
text = page.data.get("text") or page.data # big pages come back as file metadata
print(str(text)[:300])
await client.call_tool("close_instance", {"instance_id": iid})
asyncio.run(main())
Run it with the venv's interpreter (venv/bin/python scrape.py). A .py path tells FastMCP to launch the server with the current Python.
.data holds the parsed tool result in FastMCP 2.10 and later.
You now have a stealth browser you can script like Playwright, with the same tool names your agent used.
Wrap the calls in retries and you have a scraper you own end to end.
How to read the Stealth Browser MCP weekly stealth benchmark
The weekly benchmark tells you one thing: whether the browser fingerprint Stealth Browser MCP presents has regressed since the last Chrome or nodriver update. Use it as a regression alarm. It cannot tell you whether a specific protected site will let your agent through. That depends on the site's anti-bot vendor, its settings, and your IP.
What the benchmark runs
The repo runs benchmarks/stealth/ every week in GitHub Actions across seven runner setups. The October 12, 2026, run used Chrome 154 on Linux and Windows, Chrome 152 on macOS, and nodriver 0.47.0. When a row changes from one week to the next, check those version numbers first. A new Chrome release is the most likely reason a previously clean fingerprint starts leaking.
The target list covers these checks:
- Cloudflare's JS challenge on nowsecure.nl
- Sannysoft
- Intoli
- CreepJS
- Launch flags
- Input fidelity
- TLS/HTTP2 fingerprints
| Runner group | Result (Oct 12, 2026) | What failed |
|---|---|---|
| Windows x64, headed and headless | 9/9 pass | Nothing |
| macOS arm64, headed and headless | 9/9 pass | Nothing |
| Linux x64, three setups | 7/9 | Sannysoft WebGL vendor/renderer; local probe degraded |
Why the Linux runners fail, and why it matters for your server
The Linux row is the most useful result in the table. GitHub's Linux runners have no GPU, so Chrome falls back to the SwiftShader software renderer. Sannysoft reads the WebGL vendor and renderer strings, sees SwiftShader, and flags the browser right away. Real desktop users rarely report a software renderer, which is why detectors treat it as a strong bot signal.
Your Linux VPS or a Docker container without a GPU will produce the same SwiftShader string. A setup that passes cleanly on your Windows or macOS laptop can start failing the moment you move it to a cloud box, with no code changes on your side.
If you plan to run this server on a cloud box, read our guide to WebGL fingerprinting before you trust it on a protected target.
Moving off your own machine has a second cost. Two of the server's 98 tools, arbitrary in-page script execution and synthesized key events, only work for trusted local clients, and the HTTP transport has its own separate protection. If you run Stealth Browser MCP outside a local stdio setup, expect to lose those two tools. That affects any workflow that types into React forms with key events or strips overlays with injected JavaScript.
Headed vs. headless results
Headless runs scored the same as headed runs on every OS. That's a good sign for nodriver, because headless mode has historically leaked more automation signals. Detectors change faster than this table updates, so rerun it yourself on the machine you'll actually deploy to:
python -m benchmarks.stealth run --mode headed
python -m benchmarks.stealth run --mode headless --targets local_probe,init_script
Run it on the real host, with the real Chrome build and GPU situation. A pass on GitHub's runners says little about a different OS image.
What the benchmark leaves out
DataDome, Akamai, PerimeterX, and Kasada aren't on the target list. If your target runs one of those, you're the benchmark. Test against the actual site at low volume before you point an agent at it.
The Cloudflare coverage is also narrower than it looks. nowsecure.nl exercises the JS challenge, and the benchmark includes no test for interactive Turnstile or image puzzles. If those are your blockers, RobithYusuf/mcp-stealth-chrome is a separate nodriver and FastMCP server that claims a one-line Turnstile bypass, an AI-vision reCAPTCHA solver, and a curl_cffi HTTP mode that spoofs the TLS fingerprint. Those are the project's own claims, so verify them on your target the same way.
Our Cloudflare bypass guide explains why passing nowsecure.nl and passing a hardened production site are different tests.
Where a stealth browser doesn't help
This server fixes the browser fingerprint. It doesn't fix your IP, solve image CAPTCHAs, or make an LLM cheap to run at volume.
IP reputation. A clean fingerprint on a flagged IP still gets a challenge, or a block before the page loads.
The tool has a proxy argument; picking a decent IP is still your job.
CAPTCHAs. None of the 98 tools solves a puzzle CAPTCHA. Non-interactive JavaScript challenges pass because the browser looks real; a "select every bus" grid stops the run.
Scale. Each instance is a full Chrome process with a temp profile, so a laptop handles a handful in parallel.
A thousand pages an hour means a queue, a worker pool, and the script from Step 9, with no agent involved.
Token cost. Driving a browser through an LLM costs tokens on every action. Agents are good for working out a flow; repeated runs belong in code.
Trust. The server can run arbitrary JavaScript, raw CDP commands, and read every cookie. Treat whatever MCP client you connect as having full control of that browser.
A note on responsible use
Getting past a bot check doesn't change what a site allows.
Read the terms and robots.txt of anything you automate, keep request rates close to a person's, and only log in with accounts you own.
Content behind a login wall is often personal data. Collecting it can fall under privacy law even when the page technically loaded.
Troubleshooting
"No compatible browser found"
Why: The server couldn't find Chrome, Chromium, or Edge in the usual install paths.
Fix: Install one of them, then ask the agent to run validate_browser_environment_tool(). To test a specific build, set STEALTH_BROWSER_EXECUTABLE to its path.
Tools hang after spawn_browser, or return malformed JSON
Why: Something printed to stdout, and stdout is the JSON-RPC channel in stdio mode. Releases before 0.2.5 had debug print() calls doing exactly this.
Fix: Pull the latest master. Keep --debug off in normal use; when you need it, logs go to stderr.
Browser crashes on Linux, Docker, or CI
Why: Chrome's sandbox needs kernel features many containers don't expose, and running as root makes it worse.
Fix: The server detects root and containers and adjusts launch flags. If it still crashes, pass sandbox=false to spawn_browser, and only in that environment.
"Proxy URL must include both username and password"
Why: The forwarder got a proxy URL with a username but no password, or the reverse. A related error, "Invalid proxy URL (missing port)", means the URL has no port.
Fix: Use the full scheme://user:pass@host:port form. Percent-encode @, : and / inside the password.
Typed text disappears from dropdowns and autocomplete fields
Why: type_text(parse_newlines=True) fakes Enter with an untrusted JavaScript event. React-select widgets on job boards like Greenhouse and Lever ignore it and drop the value on blur.
Fix: Use press_key(), which sends trusted key events through CDP's Input.dispatchKeyEvent. It's on master but not yet in a tagged release.
FAQ
Is Stealth Browser MCP free?
Yes. The project is MIT licensed and free for commercial use. Your only costs are the LLM tokens your MCP client uses and any proxies you route it through.
Does Stealth Browser MCP bypass Cloudflare Turnstile and CAPTCHAs?
It passes Cloudflare's non-interactive JavaScript challenge on every runner in the project's weekly benchmark. It has no CAPTCHA-solving tool, so a puzzle that needs a human will stop it.
Can Stealth Browser MCP log in to sites for me?
Yes, it types credentials and clicks sign-in like a person, and user_data_dir keeps the session. It can't complete 2FA or CAPTCHAs, so do the first login by hand.
Can the stealth browser run headless?
Yes, set headless to true in spawn_browser. Headless passed every October 2026 check on Windows and macOS. On GPU-less Linux both modes leak the SwiftShader WebGL renderer.
Is it safe to expose the MCP server over HTTP?
Only on a trusted private network, and only with STEALTH_BROWSER_MCP_AUTH_TOKEN set. Start it with --transport http. Anyone who reaches the endpoint can run JavaScript in your browser.
Can I run several stealth browsers in parallel?
Yes, each spawn_browser call returns its own instance_id, and every tool takes that ID. Your RAM sets the limit, since each instance is a full Chrome process.
Wrapping up
You now have an agent that opens Cloudflare-protected pages, reads behind login modals, and keeps a saved session.
It runs through a real Chrome, trimmed to the tools you need and routed through a proxy with a matching timezone.
The pattern I'd keep: let the agent discover a flow and its hidden API calls, then replay it with the FastMCP client or a plain HTTP client.
Check the weekly benchmark after every Chrome update.
Next, run the nowsecure.nl prompt from Step 5 on your own machine. If it fails there, fix your IP or GPU before blaming the browser.