Read any web page and search the web: 28 tools, cheapest first, every page checked.
Runs on your machine from its package; the steps are below.

Not assessed yet.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx frankensurf
MCP client config
{
"mcpServers": {
"frankensurf": {
"command": "uvx",
"args": [
"frankensurf"
]
}
}
}
One read() call for AI agents, stitched from 28 web tools.
Cheapest tool first, every page checked, a receipt for every attempt.
Website · Docs · Quick start · Benchmark · Why I built it
Every web tool breaks somewhere, and they break on different sites. 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, moves 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 APIs 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, what it cost, and why it stopped. |
Python 3.12+, on Linux, macOS, or Windows through WSL. No account or key needed.
pip install "frankensurf[mcp]"
frankensurf install # every free tool: browsers, stealth browsers, local search, Crawl4AI
frankensurf read "https://www.walmart.com/search?q=air+fryer" --explain
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
frankensurf install works like playwright install: name just the tools you want
(frankensurf install search chromium), add --with-deps for the browsers' system libraries on
Linux, --list to see what's installed where, and frankensurf uninstall all to remove it. No
Docker needed. Each release is installed from PyPI on fresh Ubuntu and macOS machines and checked.
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 --recency week
frankensurf watch https://news.ycombinator.com/ --link-pattern 'item\?id=\d+'
In Claude Code as a plugin (the MCP server plus the skill that teaches Claude to use it):
/plugin marketplace add yail259/frankensurf
/plugin install frankensurf@frankensurf
From LangChain, the OpenAI Agents SDK or CrewAI (pip install "frankensurf[langchain]", and so on):
from frankensurf.agent_tools import langchain_tools # or openai_agents_tools, crewai_tools
tools = langchain_tools() # frankensurf_read, frankensurf_search
You get raw page data, not answers: text or markdown, JSON-LD, the page's own JSON, images, and
the receipt. Your agent decides what the page means. If it isn't what it wanted, it asks again with
retry_of=<trace_id> and Frankensurf skips every tool it already tried.
scripts/toolbench.py runs each tool on its own, then Frankensurf, on the same pages with the same
content checks. The newest set: 150 sites in 15 categories, picked before any was read, read once
(11 October 2026, raw rows in benchmarks/2026-10-11/).
| Tool | Sites read | Median | Cost per 1k |
|---|---|---|---|
| Plain HTTP only | 48.7% | 1.2 s | free |
| Camoufox only | 68.7% | 10.9 s | free |
| Jina Reader only | 73.3% | 1.5 s | free |
| Firecrawl only | 84.7% | 4.6 s | $4.28 |
| Zyte only | 86.0% | 10.7 s | not reported |
| Frankensurf, free tools only | 88.0% | 3.6 s | $0 |
| Frankensurf, with paid fallbacks | 96.0% | 2.9 s | $0.98 |
| Tool | Sites read | Median | Cost per 1k |
|---|---|---|---|
| Patchright only | 38.7% | 4.1 s | free |
| Headless Chromium only | 40.7% | 2.7 s | free |
| Plain HTTP only | 48.7% | 1.2 s | free |
| Camoufox only | 68.7% | 10.9 s | free |
| Scrapling only | 68.7% | 8.5 s | free |
| Jina Reader only | 73.3% | 1.5 s | free |
| Firecrawl only | 84.7% | 4.6 s | $4.28 |
| Zyte only | 86.0% | 10.7 s | not reported |
| Frankensurf, free tools only | 88.0% | 3.6 s | $0 |
| Frankensurf, with paid fallbacks | 96.0% | 2.9 s | $0.98 |
Medians are over pages read; cost is per 1,000 pages read right. ZenRows and Scrapfly are left out (their plans ran out of credits). From a fresh cloud VM, whose data-centre address sites wall more often: free tools 82.0%, lightning mode 84.0% at a 1.2 s median. See Benchmarks.
search uses free sources first and checks every result: on topic, the right kind of page, inside
the date window. Exa, Brave, Tavily, Serper and other paid sources join only when you allow paid
tools. Judged blind (results pooled and shuffled, which tool found them hidden), useful results in
the top five:
| Blind set 7 (120 queries) | Useful results (of 5) | Cost per 1k |
|---|---|---|
| Frankensurf, paid allowed | 4.67 | $9.38 |
| Exa only | 4.66 | $9.50 |
| Frankensurf, free (with the local search engine) | 3.83 | $0 |
Free, it beats Firecrawl's search: better on 35 queries, worse on 5. With paid allowed it is level
with Exa. The local search engine (frankensurf install search, SearXNG without Docker) makes the
free tier much stronger. v0.41 improved free search again: on a fresh blind set of 120
queries it beat v0.40 on 41 and lost on 12 (3.42 against 2.71 useful results of 5,
benchmarks/2026-10-14-search-v041/). Details in
benchmarks/2026-10-13-search/.
receipt.quality: good, partial or poor, with flags such as paywall,
menus or placeholder, so an agent knows when a page didn't really come through.--schema product.json (MCP schema) fills a JSON Schema from the page's JSON-LD, microdata,
meta tags and own JSON into extracted, and climbs to a stronger tool while a required field is
missing (Structured fields).--main (MCP main_content=True) adds main_text: the article without menus, footers and
banners (best with pip install "frankensurf[main]").--lightning races from the start).receipt.rescued says so.--real-browser, with its own profile, never
yours) or a second connection (FRANKENSURF_EGRESS).--site, --exclude-domain, --recency and --region on every source, and a --vertical
(news, reference, discussions, qa, code, papers, books) backed by official free APIs."Acme" OR "Acme Corp" (acquisition OR layoffs)) are judged by the
name they're about, and a weak answer is retried once with just the name.--mode merge asks every source at once and fuses the results.frankensurf search-engine start|stop|status manages the local SearXNG; search restarts it after
a reboot.frankensurf skill install adds it to ~/.claude/skills.frankensurf module discover URL --query "desk lamp" --save finds the site's search, reads the results and saves a module;
after that, searches on that site come back as structured items.frankensurf share sync starts from which tool reads which site, as the
community learned it; share export writes yours to review and contribute (opt in both ways).frankensurf watch-sites URL… --since DATE lists new pages from the feeds and sitemaps sites
publish.Everything works with no keys. Paid tools are opt-in and only come in once a site has walled a free
tool, cheapest likely success first. Set their keys (for example FIRECRAWL_API_KEY) and pass
--paid; --max-cost USD caps a read. From Python: allow_paid_fallbacks and max_cost_usd.
See Paid tools and budgets.
Every way to fetch a page is a rung: plain HTTP and markdown negotiation, browsers, stealth browsers and signed requests, hosted browsers, paid APIs, 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.
It's an alpha, and some sites still win:
Found a site it can't read? Report it; every report becomes a test case.
python3 -m venv .venv
.venv/bin/pip install -e '.[test,mcp]'
.venv/bin/frankensurf install
PYTHONPATH=.:src .venv/bin/pytest -q
The product design is in SPEC.rst and contributor rules in AGENTS.md. New tools are plugins,
never branches in Core: see Writing a plugin and the
example in examples/frankensurf-plugin-example. The website and docs live in site/.
Apache-2.0. See LICENSE.