Frankensurf
One read() call for AI agents, stitched from 28 web tools.
Every web tool breaks somewhere. Frankensurf sits in front of all of them and gives your agent one call. It tries the cheapest tool first, checks the page is real, climbs to a stronger tool only when a site pushes back, and returns clean page data with a receipt of how it got there.
Cheapest first. Plain HTTP, Jina Reader, a local browser, free stealth browsers, hosted browsers, then paid unblockers only if you allow them.
Every page checked. A challenge page, a login wall, or a search page whose results never loaded counts as a failure, not a success.
Shows its work. Every result says which tools ran, how long each took and what it cost.
Website and docs: https://frankensurf.dev · Why I built it
Try it
Python 3.12 or newer, on Linux or Windows through WSL (macOS should work but isn’t tested yet):
pip install "frankensurf[mcp]" frankensurf setup # Chromium + the free stealth browsers, a few minutes frankensurf read "https://www.walmart.com/search?q=air+fryer" --explain
No account or key needed. The read climbs until it gets the real page:
https://www.walmart.com/search?q=air+fryer ✗ http CAPTCHA 0.4s ✗ local CAPTCHA 1.5s ✓ camoufox got the page 15.6s air fryer - Walmart.com 24,192 characters via camoufox, complete (107 results), free
Drop --explain for the full JSON: text or markdown, JSON-LD, the page’s embedded and fetched JSON, images, and the receipt.
Free or paid
Everything works with no keys. Paid tools are opt-in, and only run after the free ones fail. From the benchmark below (152 sites picked before any was read):
Setup |
Sites read |
Cost per 1k |
|---|---|---|
Free tools only (frankensurf setup) |
73.7% |
$0 |
Plus paid fallbacks (Firecrawl, Zyte, …) |
89.5% |
$0.73 |
To allow paid tools, set their keys (for example FIRECRAWL_API_KEY) and pass allow_paid_fallbacks. See Paid tools and budgets.
Use it
From an agent, as an MCP server (setup for each client):
claude mcp add frankensurf -- frankensurf-mcp
From Python:
from frankensurf import Runtime
async with Runtime("state") as web:
page = await web.read("https://example.com", policy_overrides={"prefer_markdown": True})
hits = await web.search("playwright python tutorial")
From the command line:
frankensurf read https://example.com --markdown frankensurf search playwright python tutorial frankensurf watch https://news.ycombinator.com/ --link-pattern 'item\?id=\d+'
What you get back is raw page data, not answers. Your agent decides what the page means, and if it isn’t what it wanted, it can ask again with retry_of=<trace_id> to skip every tool already tried.
Also in the box: profiles (log in once, reuse the session from any tool), human handoff for CAPTCHAs and 2FA, site modules (save what your agent learns about a site as data), and Web Bot Auth request signing.
How it works
Every way to fetch a page is a rung: plain HTTP and markdown negotiation, browsers, stealth browsers and signed requests, hosted browsers, paid unblockers, and finally a person clearing the wall in a visible browser. A read starts at the cheapest rung and climbs only when a page pushes back. Every tool is a plugin, so new ones slot in as another rung. See How escalation works and the full list of integrations.
Benchmark
scripts/toolbench.py runs each tool on its own, then Frankensurf, on the same pages with the same content checks. 152 sites picked before any was read, one run on 6 October 2026, raw rows in benchmarks/2026-10-06/:
Tool |
Sites read |
|---|---|
Headless Chromium only |
32.2% |
Plain HTTP only |
46.1% |
Camoufox only |
48.7% |
Scrapling only |
53.3% |
ZenRows only |
55.3% |
Jina Reader only |
63.8% |
Zyte only |
73.7% |
Firecrawl only |
80.3% |
Frankensurf |
89.5% |
Frankensurf read 19 sites Firecrawl missed and Firecrawl read 5 Frankensurf missed. Frankensurf costs a fifth as much per page, but is slower: median 9.5 s against 5.6 s. Scrapfly is left out because its free plan ran out mid-run.
A second fresh set, 139 different sites picked on 9 October before any was read (raw rows in benchmarks/2026-10-09/):
Tool |
Sites read |
Median |
|---|---|---|
Firecrawl only |
74.1% |
6.6 s |
Frankensurf, free tools only |
81.3% |
5.6 s |
Frankensurf, with paid fallbacks |
88.5% |
9.2 s |
Frankensurf read 22 sites Firecrawl missed; Firecrawl read 2 Frankensurf missed. Firecrawl’s rate-limited pages were re-run one at a time until none were left. Racing the free tools (v0.26) then took the free tier to 82.0%, with its median down to 4.5 s and its p90 from 50 s to 37 s. See Benchmarks.
Known walls
It’s an alpha, and some sites still win:
Need the paid tools: in a free-only spot check on 9 October, Etsy, Home Depot, Yelp, Crunchbase and G2 blocked every free tool.
Beat everything so far: Shopee, Temu and Idealista.
Social sites without a login: LinkedIn, X, YouTube, Threads, Bluesky and Reddit read fine. Instagram, TikTok, Facebook and Pinterest show only the public shell (name, bio, counts); the posts need a login.
Logged-in pages (your feeds, your orders) need a profile.
Slower than one tool: a read that climbs several rungs takes 10–30 s.
Found a site it can’t read? Report it; every report becomes a test case.
Develop
From a clone:
python3 -m venv .venv .venv/bin/pip install -e '.[test,mcp]' .venv/bin/frankensurf setup PYTHONPATH=.:src .venv/bin/pytest -q
The product design is in SPEC.rst; contributor rules are in AGENTS.md. New tools are plugins, never branches in Core; see Writing a plugin. The website and docs live in site/.
Licence
Apache-2.0. See LICENSE.
Metadata
Release files for frankensurf 0.26.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| frankensurf-0.26.0.tar.gz | 429.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| frankensurf-0.26.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 709.3 kB
Release files / frankensurf-0.26.0.tar.gz
| Download URL | frankensurf-0.26.0.tar.gz |
|---|---|
| Size | 429.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ad32fbf882e7efe49eea0493ae5b491ed6cab5e46129fd073e502d72e87861c5
|
|
BLAKE2b-256 checksum How to use checksums |
cca5da2107ead44250465a1619abdfdd84955af98e6e87db995a8ec67e26e93e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / frankensurf-0.26.0-py3-none-any.whl
| Download URL | frankensurf-0.26.0-py3-none-any.whl |
|---|---|
| Size | 279.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
edc2117cf0a59fec9168e664b918fcafda3ed5aa7199492c8f6436a3929e1ed6
|
|
BLAKE2b-256 checksum How to use checksums |
cd92110302fd5079ecc23bf2c4c375b61ba7303940c41bba07d142394b85adf8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log