Skip to main content
Glama
README.md
# Stealth Web Scraper MCP

<!-- mcp-name: io.github.markkkkkkkkkkkkkkkk/stealth-scraper-mcp -->

An MCP server that fetches any web page and returns clean, token-efficient Markdown for AI agents.

- **Tiered fetching, cheapest first:** direct HTTP, browser, HTTP via residential proxy, browser via proxy. Each tier runs only when the previous one is blocked.
- **Fast path:** `curl_cffi` with real browser TLS fingerprints (Chrome, Safari, Firefox, Edge), rotated on retry.
- **Browser fallback:** when a page is blocked or needs JavaScript, it is rendered in your installed Google Chrome (or Playwright's Chromium).
- **Fewer tokens:** pass `query` to get only the passages that answer a question, ranked with BM25. Long pages are paginated.
- **Safe by default:** refuses localhost and private-network URLs, binds to `127.0.0.1`, optional API key.

## Benchmark

Measured on 30 real sites (docs, reference, news, shops, JavaScript apps and bot-protected sites), with no proxy. Each site was fetched once. Full results are in [benchmark/RESULTS.md](benchmark/RESULTS.md); re-run with `PYTHONPATH=src python benchmark/run.py`.

| | Result |
|---|---|
| Success rate, no proxy | 24/30 (80%) |
| Tokens vs raw HTML, clean Markdown | 97.9% fewer (median 90,222 → 1,901) |
| Tokens vs raw HTML, with `query` | 99.6% fewer (median 341) |
| Median time | 2.6 s HTTP, 11.4 s browser |

The 6 misses: three sites behind strict bot protection that need a residential proxy (Etsy, G2, Glassdoor), and three image-heavy homepages with little text.

## Install

Claude Code:

```bash
claude mcp add stealth-scraper -- uvx stealth-scraper-mcp
```

Claude Desktop, Cursor, VS Code and other clients (`mcpServers` config):

```json
{
  "mcpServers": {
    "stealth-scraper": {
      "command": "uvx",
      "args": ["stealth-scraper-mcp"]
    }
  }
}
```

The browser fallback uses Google Chrome if it is installed. Otherwise download Chromium once:

```bash
uvx stealth-scraper-mcp --install-browser
```

## Tool: `scrape_url_stealth`

| Parameter | Default | Purpose |
|---|---|---|
| `url` | required | Page to fetch |
| `query` | none | Return only the passages relevant to this question |
| `top_k` | 5 | Passages to return with `query` |
| `output_format` | `markdown` | `markdown`, `text`, `html` or `links` |
| `response_format` | `concise` | `detailed` adds engine, metadata and cache diagnostics |
| `max_chars` / `offset` | 15000 / 0 | Pagination; pass `next_offset` back as `offset` |
| `max_age_seconds` | 1 hour | Cache freshness; `0` forces a fresh fetch, `-1` reads only the cache |
| `render_js` | `auto` | `always` or `never` force or skip the browser |
| `proxy` | `auto` | `auto` uses the residential proxy only after a block; `never` or `always` |
| `css_selector` | none | Only return matching elements |
| `wait_for_selector`, `scroll` | none / false | Browser options for dynamic and lazy-loaded pages |

Errors come back as `[code] message Hint: ...`, e.g. `blocked`, `timeout`, `url_not_allowed`, `selector_not_found`.

## Running over HTTP

```bash
uvx stealth-scraper-mcp --transport http --port 8080
```

The endpoint is `http://127.0.0.1:8080/mcp`, with `/health` for checks. To expose it on a network, set an API key first:

```bash
SCRAPER_API_KEY=change-me SCRAPER_HOST=0.0.0.0 uvx stealth-scraper-mcp --transport http
```

Clients then send `Authorization: Bearer change-me`.

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `SCRAPER_PROXY_URL` | none | Residential proxy, used only after a direct fetch is blocked |
| `SCRAPER_API_KEY` | none | Require a bearer token on HTTP |
| `SCRAPER_HOST` / `SCRAPER_PORT` | `127.0.0.1` / `8080` | HTTP bind address |
| `SCRAPER_CACHE_TTL` | `3600` | Default cache freshness in seconds |
| `SCRAPER_CACHE_MAX_ENTRIES` | `256` | Cache size |
| `SCRAPER_BROWSER_CHANNEL` | `chrome` | Browser to launch; empty for bundled Chromium |
| `SCRAPER_BROWSER_HEADLESS` | `true` | `false` runs a visible, harder-to-detect browser |
| `SCRAPER_BROWSER_LOCALE` / `SCRAPER_BROWSER_TIMEZONE` | `en-US` / system | Keep these consistent with your proxy's location |
| `SCRAPER_ALLOW_PRIVATE_NETWORKS` | `false` | Allow localhost and private IPs |
| `SCRAPER_FETCH_TIMEOUT`, `SCRAPER_MAX_RETRIES` | `20`, `2` | Request timeout and retries |

## Plugins

Packages can extend the server through the `stealth_scraper_mcp.plugins` entry point group. They can add usage metering, extra HTTP routes and CLI commands; see `src/stealth_scraper_mcp/plugins.py`. Without plugins the server is free and unmetered.

## Responsible use

Respect each site's terms of service and robots.txt, and avoid heavy request rates.

## License

[AGPL-3.0-or-later](LICENSE). You can use, modify and self-host this freely. If you offer a modified version to others as a network service, you must publish your changes under the same license.

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

With a single tool there is no possibility of selection confusion; an agent cannot mistake scrape_url_stealth for anything else. The description clearly scopes the tool's behavior (query filtering, JS fallback, caching, pagination).

Naming Consistency5/5

The lone name scrape_url_stealth follows a clear verb_noun_modifier snake_case pattern. No competing conventions exist to create inconsistency.

Tool Count3/5

One tool is thin for a server branded as a scraper platform, even though that tool is genuinely capable. It lands in the borderline zone where a single entry point handles everything but leaves no room for distinct capabilities.

Completeness3/5

Core single-page fetch, JS rendering, caching, and pagination are covered, but common scraping needs are absent: batch/multi-URL fetch, crawling, sitemap/robots handling, and structured or raw HTML output modes. Agents can work around these, but the surface is not fully complete for the stated domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues