# πŸ‘οΈ EyeBrowse ### A stealthy, LLM-drivable browser engine β€” one codebase, two faces. A **Python library** *and* an **MCP server** for driving a real, hard-to-detect browser, so legitimate automation isn't false-flagged or IP-banned by Cloudflare, DataDome, Akamai, or PerimeterX. Built on **CloakBrowser** β€” a stealth **Chromium** (Chrome/146) that's a Playwright drop-in β€” so EyeBrowse gets the full **Chrome DevTools Protocol**: trusted cursorless clicks, deep network inspection, MHTML, PDF, and native video. [![CI](https://github.com/Evil-Bane/eyebrowse/actions/workflows/ci.yml/badge.svg)](https://github.com/Evil-Bane/eyebrowse/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/eyebrowse?color=3775A9&logo=pypi&logoColor=white)](https://pypi.org/project/eyebrowse/) [![Python 3.12](https://img.shields.io/badge/python-3.12-3776AB?logo=python&logoColor=white)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE)
[![MCP tools](https://img.shields.io/badge/MCP-85_tools-7c3aed.svg)](docs/TOOLS.md) [![Engine: CloakBrowser](https://img.shields.io/badge/engine-CloakBrowser%20(stealth%20Chromium)-4285F4.svg)](https://pypi.org/project/cloakbrowser/) [![Code style: Ruff](https://img.shields.io/badge/lint-ruff-261230.svg?logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-22c55e.svg)](CONTRIBUTING.md) ![EyeBrowse β€” an AI agent driving a stealth browser past Cloudflare, over MCP](https://raw.githubusercontent.com/Evil-Bane/eyebrowse/master/docs/demo.gif) β–Ά Full-quality MP4: docs/demo.mp4 β€” an AI agent drives EyeBrowse over MCP: clears a Cloudflare check, then reads real docs (asyncio Β· httpx Β· MDN).
--- ## Why EyeBrowse? - πŸ₯· **Stealth by default** β€” engine-level fingerprint spoofing (`geoip` + `humanize` on out of the box, novel fingerprint per launch); `navigator.webdriver` masked; viewport auto-sized to the spoofed screen. No `puppeteer-extra` band-aids β€” the anti-detection is *compiled into the browser*. - πŸ€– **Built for LLMs** β€” pages are read as an **ARIA tree with `[ref=…]` handles**; the model acts by ref (`click`/`type`/`hover`), not by brittle CSS or raw pixels. Cross-origin iframes, shadow DOM, popups β€” handled. - ⚑ **Chrome DevTools Protocol** β€” **trusted, cursorless clicks** by node ref (`Input.dispatchMouseEvent`), raw `Network`/`Performance`/`Emulation` access, **MHTML** snapshots, **PDF** export, and **native video** β€” all reachable as tools. - 🧰 **Library *and* MCP from one codebase** β€” a clean Python API (`EyeBrowse` + `Session`), mirrored 1:1 by a thin **MCP server** (**85 `browser_*` tools**) for Claude Code and any MCP client. - πŸͺŸ **Never boxed in** β€” the curated high-level API doesn't hide Playwright: reach `session.page` / `.context` / `.browser` for anything it doesn't wrap. - πŸ”‹ **Batteries included** β€” multi-session, proxy + identity rotation, API-mode captcha solvers, native video, full **HAR** capture, and clean-markdown extraction. > **Scope.** EyeBrowse is a low-level browser *engine* β€” it holds **no workflow logic**. > Consumers decide *what* to do; the engine provides *what's possible*. ## Contents [Quickstart](#quickstart) Β· [Install](#install) Β· [Features](#features) Β· [Compare](#how-eyebrowse-compares) Β· [Library](#use-as-a-library) Β· [MCP](#use-over-mcp) Β· [Proxy & identity](#proxy--identity-optional) Β· [Extraction](#extraction) Β· [Recording](#recording) Β· [How it works](#how-it-works) Β· [Caveats](#caveats) Β· [Tools](docs/TOOLS.md) Β· [License](#license) ## Quickstart ```bash pip install eyebrowse # The stealth-Chromium binary downloads automatically on first launch β€” nothing else to run. ``` ```python import asyncio from eyebrowse import EyeBrowse async def main(): eb = EyeBrowse() # stealth defaults: geoip Β· humanize async with eb.session() as s: await s.navigate("https://example.com") print(await s.snapshot()) # ARIA tree with [ref=...] handles await s.click("e6") # act on a ref from the snapshot await eb.aclose() asyncio.run(main()) ``` …or wire it into **Claude Code** (or any MCP client) β€” see [Use over MCP](#use-over-mcp). ## Install **From PyPI** ```bash pip install eyebrowse # or: uv pip install eyebrowse # CloakBrowser fetches its Chromium binary lazily on first launch β€” nothing to run. pip install "eyebrowse[extract]" # optional: + Crawl4AI markdown extraction (heavier) ``` **From source (development)** ```bash git clone https://github.com/Evil-Bane/eyebrowse && cd eyebrowse uv sync # core engine (add --extra extract for Crawl4AI) cp .env.example .env # only if you use a proxy / captcha keys ``` Python 3.12 (pinned `<3.13`). Engine: `cloakbrowser>=0.3` (stealth Chromium, Chrome/146), on `playwright 1.60` and `mcp 1.27`. ## Features | | | |---|---| | πŸ₯· **Stealth** | CloakBrowser's patched-Chromium fingerprint spoofing (novel `--fingerprint` per launch); `geoip` + `humanize` by default; `webdriver` masked; viewport matched to the spoofed screen. | | πŸ€– **LLM interaction** | `aria_snapshot(mode="ai")` β†’ ARIA tree + `[ref]` handles; click / type / hover / select / drag / file-upload / dialogs / keyboard; coordinate mouse too. | | ⚑ **CDP** | **trusted cursorless click** by ref, raw CDP (`Network` / `Performance` / `Emulation`), **MHTML** capture, **PDF** export. | | πŸͺŸ **Frames & DOM** | cross-origin iframe routing by ref, shadow-DOM piercing, popup/new-tab switching, `evaluate` inside any frame. | | πŸ—‚ **Multi-session** | independent stealth sessions, each with its own context / identity / proxy. | | 🌐 **Network** | inspect requests/responses (incl. XHR/fetch bodies & WebSocket frames), block URLs, mock responses, go offline, full **HAR** export. | | πŸ’Ύ **State** | cookies, localStorage & sessionStorage (CRUD), `storage_state` save/reload. | | πŸͺͺ **Identity rotation** | fresh fingerprint + isolated profile + paired proxy; pluggable residential `ProxyProvider`. | | 🧩 **Captcha** | pluggable **API-mode** solvers (CapSolver / 2Captcha / CapMonster / NextCaptcha) + TOTP β€” no browser extension. | | πŸ“„ **Extraction** | Crawl4AI `raw:` feed β†’ clean, token-efficient **markdown** (no LLM, no API keys). | | πŸŽ₯ **Capture** | screenshots, Playwright tracing, and **native video** (`.webm`). | | βœ… **Verify & debug** | assertions, element highlighting, locator generation, geolocation/header emulation. | Full per-tool reference: **[docs/TOOLS.md](docs/TOOLS.md)** (85 tools across 18 groups). ## How EyeBrowse compares | | EyeBrowse | Playwright MCP | browser-use | playwright-stealth | | :------------------------------------------------ | :----------------: | :-----------------: | :----------------: | :----------------: | | Anti-detection **compiled into the browser** | βœ… | ❌ | ❌ | ⚠️ JS patches | | LLM-native ARIA **`[ref]`** interaction model | βœ… | βœ… | βœ… | ❌ | | Ships an **MCP server** | βœ… (85 tools) | βœ… | ⚠️ partial | ❌ | | One codebase: Python **library *and* MCP** | βœ… | MCP-only | lib-only | lib-only | | Full **CDP** (trusted clicks Β· network Β· MHTML Β· PDF Β· video) | βœ… | ⚠️ partial | ❌ | ⚠️ partial | | **Captcha** (API-mode) + TOTP | βœ… | ❌ | ❌ | ❌ | | **Proxy + identity rotation** built in | βœ… | ❌ | ⚠️ partial | ❌ | | Cross-origin iframes Β· shadow DOM Β· popups | βœ… | βœ… | ⚠️ partial | n/a | Fair-use note: each project targets a different niche β€” this compares them on the axes EyeBrowse optimizes for (stealth + LLM-drivable + one library/MCP codebase), not as an overall ranking. ## Use as a library ```python import asyncio from eyebrowse import EyeBrowse async def main(): eb = EyeBrowse() # stealth defaults try: async with eb.session() as s: # a stealth session (auto-closed) await s.navigate("https://example.com") print(await s.snapshot()) # ARIA tree with [ref=...] handles await s.click("e6") # act on a ref await s.type("e8", "hello", submit=True) png = await s.screenshot(full_page=True) title = await s.page.title() # full Playwright power when you need it finally: await eb.aclose() asyncio.run(main()) ``` Run the included proof: `uv run python examples/direct_usage.py`. ## Use over MCP EyeBrowse ships an MCP server (`eyebrowse-mcp`, FastMCP over stdio). Add it to any MCP client. **Claude Code (CLI):** ```bash claude mcp add eyebrowse -- eyebrowse-mcp ``` **Any MCP client (JSON config):** ```json { "mcpServers": { "eyebrowse": { "command": "eyebrowse-mcp" } } } ``` Then drive the loop: `browser_navigate(url)` β†’ read the snapshot β†’ act by ref (`browser_click` / `browser_type` / …). A default session is auto-created, so most tools just work. Full list: **[docs/TOOLS.md](docs/TOOLS.md)**. ## Proxy & identity (optional) Runs **proxyless by default** (`geoip` still aligns locale/timezone to your real IP). Add a proxy only when you want one: ```python await eb.new_session(proxy="http://user:pass@residential.example:8080") await eb.rotate_identity(proxy="socks5://host:1080") # fresh fingerprint + paired IP await eb.new_session(no_proxy=True) # force proxyless ``` Set a default once via `EYEBROWSE_PROXY_*` in `.env`, `eb.set_static_proxy(...)`, or a custom `ProxyProvider` for rotation. Over MCP: `browser_new_session(proxy_url=…)` / `browser_new_identity(proxy_url=…)` / `browser_set_proxy(…)`. > **reCAPTCHA v3 / reputation gates** are score-based and key off IP + session reputation β€” a > fresh browser on a flagged IP fails regardless of stealth. Pair EyeBrowse with a clean > residential proxy. ## Extraction `eb.extract()` (or `browser_extract`) hands the rendered HTML to Crawl4AI's `raw:` feed and returns clean, pruned **markdown** β€” **no LLM is called and no LLM keys are ever read**; the consuming agent does any structuring. ```python md = await eb.extract() # markdown string res = await eb.extract(output_path="data/page.md") # β†’ {"path": ..., "chars": ...} ``` ## Recording **Native video** β€” Playwright records the whole session to a `.webm`, written on close. The path is known up-front; the file finalizes when the session closes: ```python s = await eb.new_session(record_video=True) # ... drive the browser ... print(await s.video_path()) # path is known up-front; file finalizes on close await eb.close_session(s.id) ``` Over MCP: `browser_new_session(record_video=True)` β†’ `browser_video_path`. Want a GIF for a README? Convert the `.webm` with ffmpeg (`ffmpeg -i demo.webm demo.gif`). The demo at the top was captured this way β€” see **`examples/make_demo.py`**. ## How it works ``` CONSUMERS ENGINE (library: eyebrowse/) Claude Code ──MCP──▢ mcp/ ──▢ EyeBrowse faΓ§ade (public API) your code ─ import ──────────▢ β”œβ”€ BrowserEngine (CloakBrowser / stealth Chromium) any MCP client β”œβ”€ proxy / identity rotation (pluggable) β”œβ”€ captcha solvers (pluggable, API-mode) └─ Crawl4AI (raw: feed) β†’ clean markdown ``` The faΓ§ade (`EyeBrowse` + `Session`) is the product; the MCP adapter is a thin 1:1 wrapper over it. The high-level API is curated and LLM-friendly β€” *not* a reimplementation of all of Playwright β€” and the raw `page` / `context` / `browser` objects are always one attribute away. The launcher is the only engine-specific layer; everything else is plain Playwright. ## Caveats Worth knowing: - **`evaluate`** runs in the page's main world (page globals reachable). To override a page's widget globals and fire a site callback (e.g. for captcha), EyeBrowse injects a `