[ STEALTH BROWSER AUTOMATION ]

Stealth browser automation that anti-bot systems do not flag

Stealthly is an MCP server that hands an AI agent a real Chrome — the browser you already have installed, driven over the Chrome DevTools Protocol, with the flags that mark a session as automated removed before it launches. This page is the long version: what stealth browser automation means, which signals actually give an automated browser away, and where the approach stops working.

[ DEFINITION ]

A real Chrome, not a patched headless shell

Most browser automation starts by launching a browser with a pile of flags that exist for the convenience of the automation harness: disable the GPU because the CI box has none, use a mock keychain because there is no desktop keyring, announce automation so the browser suppresses the password manager. Each of those is a convenience, and each of them is visible from inside the page. A script running on the site you visit can read them back out.

Stealthly is built on nodriver, a CDP-based driver, and it spawns the Chrome, Chromium or Edge already installed on the machine. There is no bundled engine and no patched headless binary. What the page sees is a browser that was configured like a browser, because it was one. Before launch the server filters the argument list and removes 30+ flags that would compromise that, reporting each removal back to the caller in spawn_diagnostics.stealth_args_stripped so nothing is stripped silently.

That is the whole trick, and it is worth being precise about how modest it is: this is a user-space tool that is careful about how it starts a normal browser and what state that browser carries. It is not a kernel driver and it does not hook the operating system. Why that is the right altitude for web bot detection is its own page.

[ THE TELLS ]

The four classes of tell, and what happens to each

The README ships this as a table because the categories are genuinely distinct — they leak through different mechanisms and a page detects them in different ways.

CategoryExamplesWhy it is stripped
Automation signals--enable-automation, --test-typeSets navigator.webdriver=true
Fingerprint leaks--disable-gpu, --disable-webglDetectable via WebGL and canvas probes
Puppeteer defaults--disable-backgrounding-occluded-windowsBot signature fingerprint
Playwright defaults--password-store=basic, --use-mock-keychainBot signature fingerprint

The first row is the one everybody knows: --enable-automation flips navigator.webdriver to true, and a single line of JavaScript reads it. The other three are the ones that catch people who already removed the first. A browser with the GPU disabled reports a software renderer through the WebGL vendor and renderer strings, and hashes differently on a canvas draw, which is a difference a fingerprinting script can score without ever asking a direct question. The Puppeteer and Playwright rows are pure signature: those flags are not detectable because of what they do, they are detectable because of which tool sets them by default.

Passing a bad flag on purpose is a reasonable way to see the filter work. spawn_browser(headless=True, browser_args=["--enable-automation"]) returns with stealth_args_stripped listing the flag and the reason it went.

The full mechanics of what a detector runs, probe by probe, are on how bot detection works.

[ HEADLESS OR HEADED ]

Where the window actually opens

A headed browser appears on the desktop of the process that launched it, not the desktop of whoever asked for it. That sounds like trivia until sessions share a backend: a backend first started from an SSH login or a Windows service session cannot show a window to anyone, including the sessions running on the physical screen in front of you. The usual result is a browser that exists, reports success, and is invisible.

So backends here are keyed by display context: one per desktop, plus one for headless. Discovery prefers a backend that can show a window, which means an SSH-driven spawn_browser(headless=False) is routed to the desktop backend and its window opens on the real screen. Where no such backend exists, the spawn raises rather than handing back an invisible browser — a loud failure instead of a silent one. stealth-chrome-devtools doctor prints which contexts currently have a backend.

Headless spawns work from anywhere: SSH, CI, services. If your automation never needs to be watched, none of the above applies to you.

[ SESSIONS ]

Staying logged in between runs

A fresh profile on every run is itself a signal, and more practically it means solving a login on every run. Stealthly uses a master / snapshot / clone strategy instead. spawn_browser() uses the master profile when it is free; before opening master the server refreshes master-snapshot as a safe copy; when master is busy, a clone is made from that snapshot. Clones carry the cookies, logins and Web Data of the original.

  1. Start the server and call spawn_browser() with no user_data_dir.
  2. Sign in to your accounts in the browser that opens.
  3. Close it. Future sessions use that profile, or clone from its snapshot.

Clones exclude regenerable Chrome caches, so each is a few MB rather than multiple GB, and disposable auto-clones are deleted on close. Profiles you name explicitly — spawn_browser(user_data_dir="github-session") — persist and are never deleted. Ask for a name that is already in use and it auto-suffixes to github-session-2 rather than fighting over the directory.

Storage is capped rather than trusted. STEALTH_MCP_CLONE_STORAGE_CAP_GB (default 10 GB) reclaims the oldest idle clones, and eviction is recoverable: the clone moves to sessions/.trash/ and is purged only after STEALTH_MCP_CLONE_TRASH_RETENTION_HOURS (default 24 h). STEALTH_MCP_BROWSER_SESSION_STORAGE_CAP_GB (default 20 GB) trims the largest idle named profiles of their regenerable directories with every login preserved. In-use profiles are never touched.

[ TOOL SURFACE ]

What the agent can do once it is in

The server exposes 94 tools across 11 sections, a count derived from the live tool registry and asserted by a test rather than maintained by hand. The sections are browser_management, cdp_functions, cookies_storage, debugging, dynamic_hooks, element_extraction, element_interaction, file_extraction, network_debugging, progressive_cloning and tabs.

A representative handful: spawn_browser, navigate, take_screenshot, execute_script, query_elements, click_element, type_text, get_page_content, list_instances, close_instance, list_network_requests, get_cookies and set_cookie. Underneath them is the full CDP surface — DOM manipulation, network interception, JavaScript execution, screenshots, cookies.

What the server serves is not the same as what the release gate proves, and the project publishes both numbers. At the release SHA in the evidence ledger, 3 of the 94 are release-qualified — asserted end to end over the real stdio transport a client actually speaks. The rest are driven against real Chrome by the E2E suite, but through an in-process seam, so at the wire they are served-unqualified: tested, not proved there. `RELEASE_CONTRACT.md` lists the state of each tool and is the only source for those numbers.
[ LIMITS ]

Where this is not the right tool

Stealth automation is powerful, and the honest framing is that it is for work you are authorised to do: testing, QA, accessibility auditing, and automating tasks against systems you are entitled to use. Site terms of service, robots directives and the applicable law all still apply, and none of them are a technical question.

There are also plain technical limits. This drives Chromium-family browsers, so a Firefox or WebKit target is out of scope. It is a Python package and an MCP server, so it fits an agent calling tools, not a compiled test suite in another language. And it addresses what a page can observe about a browser — not software that inspects the machine the browser runs on.

One operational note that matters on shared hardware: the browser-session root defaults to a drive-root directory holding your logged-in cookies and session data. On a shared multi-user Windows box, point STEALTH_MCP_BROWSER_SESSION_ROOT somewhere inside your user profile so the OS ACLs protect it.

[ COMMON QUESTIONS ]

Three questions worth answering directly

Does it work headless?

Yes. Headless spawns work from anywhere, including an SSH login, CI and Windows services. Headed browsing is the case with a requirement attached: the window has to open on a real desktop.

Which browsers does it support?

Chrome, Chromium or Microsoft Edge. It drives a real installed browser over the Chrome DevTools Protocol rather than shipping its own engine.

Do I need a desktop session?

Only for headed browsing. Backends are keyed by display context, and a headed spawn is routed to a backend that can actually show a window. Where no such backend exists the spawn raises an error instead of handing back an invisible browser.

[ GET STARTED ]

Install in about a minute

terminal
uv tool install stealth-chrome-devtools-mcp==2.1.14

That installs a version-pinned executable; point your MCP client's config at it and restart the client. The full install page covers the config block, the Claude Code one-liner, why uvx in a client config is the wrong shape for a fleet, and the environment variables.