Skip to main content
Glama
README.md
# wraith-mcp

[![PyPI](https://img.shields.io/pypi/v/wraith-mcp)](https://pypi.org/project/wraith-mcp/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

AI-native stealth browser MCP server. Tell it what to do — it figures out how.

[Browser Use](https://github.com/browser-use/browser-use) (AI vision navigation) + [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright) (bot detection bypass).

## Why This One?

| | wraith-mcp | playwright-mcp | stealth-browser-mcp | browser-use-mcp-server |
|---|---|---|---|---|
| Navigation | **AI vision** (self-healing) | CSS selectors | CSS selectors | AI vision |
| Bot detection bypass | **Patchright** (binary-level) | None | nodriver | None |
| Tools | **17** — keyless `navigate/snapshot/click/type_text/…` + AI `browse`/`extract` | 20+ | 90+ | via Agent |
| Keyless (no API key) | **Yes** — client AI drives low-level tools directly | Yes | Yes | No |
| Site layout changes | **Adapts automatically** | Breaks | Breaks | Adapts |
| LLM providers | **6** (Anthropic, OpenAI, OpenRouter, Google, Ollama, +compatible) | N/A | 1 | 1 |

**One command does it all** — no selectors, no step-by-step scripting:

```
"Log into my dashboard and download the monthly report"
```

## Quick Start

> Add wraith-mcp as MCP server

## Install

```bash
pip install wraith-mcp
```

## Setup

Add to your MCP config (`.mcp.json`, `.cursor/mcp.json`, `.windsurf/mcp.json`, etc.):

```json
{
  "mcpServers": {
    "wraith": {
      "command": "wraith-mcp"
    }
  }
}
```

Works with any MCP client: Cursor, Windsurf, VS Code, Cline, Roo Code, OpenCode, Codex, and more.

The browser window is **visible by default**. Set `"HEADLESS": "true"` in `env`
to run it headless — recommended for servers, CI, and Docker (the Docker image
already sets it).

These clients call Wraith over MCP. The low-level tools work with no API key —
your client's AI drives them directly. The autonomous `browse`/`extract` tools
use MCP sampling when supported, otherwise a fallback key (see below).

## Do I Need an API Key?

**No — for the low-level tools.** `navigate`, `snapshot`, `click`, `type_text`,
`scroll`, `press_key`, `get_content`, `screenshot`, `pdf`, and `tabs` need **no
API key**: your MCP client's model (Claude Code, Codex, OpenCode, …) calls them
directly and is the brain, exactly like playwright-mcp.

Only the **autonomous Agent tools** — `browse` and `extract`, which plan the
steps themselves — need a model. They use MCP sampling when the client supports
it, otherwise a fallback provider key. Note that many clients, **including Claude
Code today, do not implement MCP sampling**, so `browse`/`extract` need a
fallback key there (`ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`,
`GOOGLE_API_KEY`, or Ollama via `OLLAMA_MODEL`). The low-level tools work either way.

## Tools

**Keyless** — your client's AI drives the browser directly, no API key:

| Tool | Description |
|------|-------------|
| `navigate` | Open a URL; returns the page's interactive elements |
| `snapshot` | List interactive elements as `[index] <tag> text` |
| `click` | Click an element by index (from `snapshot`) |
| `type_text` | Type text into an element by index |
| `scroll` | Scroll the page |
| `press_key` | Press a key or chord (e.g. `Enter`, `Control+a`) |
| `get_content` | Current page as clean markdown |
| `screenshot` | Capture a page as base64 PNG |
| `pdf` | Render a page to base64 PDF (headless only) |
| `tabs` | List, open, or close tabs |
| `list_downloads` | List files downloaded this session (saved to `./downloads` by default) |
| `save_storage_state` | Save cookies + localStorage to a file (restore via `BROWSER_STORAGE_STATE`) |
| `list_sessions` / `close_session` / `close_all_sessions` | Manage persistent sessions |

**Autonomous AI Agent** — needs MCP sampling or a fallback key:

| Tool | Description |
|------|-------------|
| `browse` | Execute any browser task in natural language |
| `extract` | Pull structured data from a page |

The low-level tools share a persistent browser via `session_id` (default
`"default"`): call `navigate` once, then `snapshot`/`click`/`type_text`/… reuse
the same page. `browse`/`extract` also accept `session_id`.

## Fallback LLM Providers

Use these only when your MCP client does not support sampling, or when you want
to force Wraith to use an explicit provider.

| Provider | Key |
|----------|-----|
| Anthropic (default) | `ANTHROPIC_API_KEY` |
| OpenRouter | `OPENROUTER_API_KEY` |
| OpenAI | `OPENAI_API_KEY` |
| DeepSeek / Groq / Together | `OPENAI_API_KEY` + `OPENAI_BASE_URL` |
| Google Gemini | `GOOGLE_API_KEY` |
| Ollama (local) | `OLLAMA_MODEL` |

Set `BROWSER_USE_MODEL` to override the default model or provide a sampling model
hint to the MCP client.

## Browser Options

Wraith keeps its default browser profile unless optional env vars are set. It can
forward Browser Use profile knobs for domain policy (`BROWSER_ALLOWED_DOMAINS`,
`BROWSER_PROHIBITED_DOMAINS`, `BROWSER_BLOCK_IP_ADDRESSES=true` for Browser
Use's direct-IP navigation block), session/artifacts (`BROWSER_STORAGE_STATE`,
`BROWSER_USER_DATA_DIR`, `BROWSER_DOWNLOADS_PATH`, `BROWSER_RECORD_HAR_PATH`,
`BROWSER_RECORD_VIDEO_DIR`, `BROWSER_TRACES_DIR`), and permissions, viewport, or
wait timing (`BROWSER_PERMISSIONS`, `BROWSER_VIEWPORT`,
`BROWSER_MINIMUM_WAIT_PAGE_LOAD_TIME`,
`BROWSER_WAIT_FOR_NETWORK_IDLE_PAGE_LOAD_TIME`, `BROWSER_WAIT_BETWEEN_ACTIONS`)
when the installed Browser Use `BrowserProfile` supports those fields. Domain/IP
policy env vars fail closed on unsupported Browser Use versions.

**Locale & timezone (stealth):** by default Wraith pins neither — it inherits the
host locale and timezone so they stay consistent with your exit IP. Forcing
`en-US` on a non-US IP is itself a bot signal, so set these only when routing
through a proxy: point `BROWSER_LOCALE` and `BROWSER_TIMEZONE` at the proxy's
region (e.g. `en-US` + `America/New_York`) so locale, timezone, and IP agree.
`BROWSER_TIMEZONE` is applied via CDP (`Emulation.setTimezoneOverride`) because
Browser Use's `BrowserProfile` has no timezone field.

**Downloads:** files save to `./downloads` (relative to the server's working
directory) by default so they persist — Browser Use otherwise uses a temp dir
that gets cleaned up on session stop, losing the file. Override with
`BROWSER_DOWNLOADS_PATH`, and use the `list_downloads` tool to retrieve the saved
paths after a `click`/`navigate` triggers a download.

## Docker

```bash
docker build -t wraith-mcp .
docker run -i --rm wraith-mcp
```

SSE mode for local-only testing:

```bash
docker run -p 127.0.0.1:8808:8808 wraith-mcp --transport sse --host 0.0.0.0 --port 8808
```

Do not expose the SSE port directly to an untrusted network. If you need remote
access, put it behind an authenticated proxy or SSH tunnel and restrict browsing
with `BROWSER_ALLOWED_DOMAINS` plus `BROWSER_BLOCK_IP_ADDRESSES=true`.

## SSE Transport

```bash
wraith-mcp --transport sse --host 127.0.0.1 --port 8808
```

The SSE host defaults to `127.0.0.1`. Binding to `0.0.0.0` is only appropriate
behind an authenticated proxy or another trusted network boundary.

## How It Works

**Keyless (low-level tools)** — your MCP client's AI is the brain:

```
Client AI -> MCP Server -> Patchright Chromium
```

1. `navigate` opens a page and returns its interactive elements
2. The client AI reads the `[index] <tag> text` snapshot and decides what to do
3. It calls `click`/`type_text`/`scroll`/… by index — no model runs in Wraith
4. Patchright executes without triggering bot detection

**Autonomous (`browse`/`extract`)** — Wraith drives a Browser Use Agent:

```
AI Agent -> MCP Server -> Browser Use Agent -> Patchright Chromium
```

1. Describe a task in natural language
2. Browser Use asks the MCP client model through sampling, or a fallback provider
3. Browser Use sees the page (screenshot + DOM) and decides actions
4. Patchright executes without triggering bot detection

## Security

- URL scheme validation (http/https only)
- `max_steps` capped at 50 server-side
- Input length capped at 4000 chars
- Task timeout (default 120s, configurable via `BROWSER_TASK_TIMEOUT`)
- Proxy support via `PROXY_SERVER`
- SSE transport binds to `127.0.0.1` by default; do not expose it directly
  without authentication
- Browser Use page context is sent to the MCP client model via sampling, or to
  the configured fallback provider

## Limitations

- Binary-level stealth only (no `Runtime.enable` CDP fix)
- Enterprise WAFs may still block without residential proxies
- Fresh browser per call (~3s startup)
- The low-level tools (`navigate`/`snapshot`/`click`/…) are keyless — no model needed
- `browse`/`extract` need MCP sampling or a fallback key; many clients (incl. Claude Code) don't implement sampling, so a fallback key is required there

## License

MIT

TDQS

A3.7/5.0

Scored across 17 tools

Disambiguation4/5

Most tools have clearly distinct purposes: navigation, interaction, content reading, screenshots, PDFs, downloads, and session management are all separate. The only potential confusion is between browse, extract, and navigate, but their descriptions make the distinction clear.

Naming Consistency3/5

Naming is mixed: some tools use bare verbs (browse, scroll, navigate, click, extract), others use verb_noun (type_text, get_content, close_session), and a couple are nouns used as commands (tabs, screenshot). The style is readable but not uniform.

Tool Count4/5

With 17 tools, this is slightly above the ideal 3-15 range, but each tool covers a distinct browser or session operation, making the count justified. It feels a bit heavy but not excessive.

Completeness4/5

The server covers the core browser lifecycle well: navigation, interaction, content extraction, screenshots, PDFs, downloads, persistent sessions, and tabs. Minor gaps exist (e.g., no explicit history or wait commands), but workflows are largely complete.

Maintenance

ActivityInactive
ResponsivenessNo issues