jevcrawl
The web crawler for AI agents that finds the answer, not just pages.
A small decision model picks every link and every passage, so your agent's LLM reads only what answers its question at a few cents per crawl.
Quick start · Benchmarks · Tools · How it works · Configuration
Why jevcrawl
Agents that browse a site have two bad options. They can read everything a crawler fetches, which buries the answer in navigation and boilerplate and burns tokens. Or they can let the LLM pick each link, which is slow and costs dollars per question. jevcrawl does neither:
🎯 Finds the answer page more often. 54% of tasks vs 33% when Claude picks the links, and 2x as often as Crawl4AI's goal-directed crawl.
💸 About 8x cheaper per task, 10x per correct answer. The crawl decisions cost about a cent.
⚡ 2x faster than an LLM-driven crawl. Four pages at a time, one decision request per page.
📜 Verbatim, with sources. Every passage is copied from the page with its URL, heading path and character offsets. The decision model only scores, so it cannot invent text.
🛡️ Safe against prompt injection by design. Page text is only ever scored, never followed.
🔌 Works everywhere MCP does: Claude Code, Claude Desktop, Cursor, or the CLI.
Related MCP server: Site Knowledge
Quick start
You need a Jev API key from console.typesafe.ai and uv.
Claude Code
claude mcp add jevcrawl -e TYPESAFE_API_KEY=<your key> -- uvx jevcrawlClaude Desktop, Cursor and other MCP clients (mcpServers in the client's config file)
{
"mcpServers": {
"jevcrawl": {
"command": "uvx",
"args": ["jevcrawl"],
"env": { "TYPESAFE_API_KEY": "<your key>" }
}
}
}Then ask your agent something like "Use jevcrawl to find which Python version added the walrus
operator, starting from docs.python.org". Check the setup with
uvx jevcrawl doctor. Agents that load skills
can also use skills/jevcrawl/SKILL.md.
Pages that need JavaScript (optional). jevcrawl reads plain HTML. To also render pages that
are empty without JavaScript, add the js extra and download Chromium once:
claude mcp add jevcrawl -e TYPESAFE_API_KEY=<your key> -- uvx --from "jevcrawl[js]" jevcrawl
uvx --from "jevcrawl[js]" playwright install chromium(In mcpServers, use "args": ["--from", "jevcrawl[js]", "jevcrawl"].) Only pages detected as
JavaScript shells are opened in the browser, at most five per call, and they are listed in
stats.js_rendered_urls. Everything else is still fetched as plain HTML.
Benchmarks
Every system starts on a site's home page with the same 30-page budget and the same question. What each one collects goes to the same answering LLM, and the same judge grades the answer, so the crawler is the only thing that changes.
jevcrawl vs Crawl4AI
Crawl4AI is one of the most popular open-source crawlers
for LLMs. Its BestFirstCrawlingStrategy with a keyword scorer is its way to crawl toward a question
without an LLM, so we compared the two on whether they fetch the page that holds the answer.
Where the numbers come from
Tasks | WebWalkerQA from WebWalker: Benchmarking LLMs in Web Traversal (Wu et al., Alibaba Tongyi Lab, ACL 2025): questions whose answers sit several clicks deep in real websites (conferences, universities, organizations, games), in English and Chinese. |
Task selection | Each task was re-checked live before the run: the site and answer page still load and still contain the reference answer. Tasks looked at while developing are excluded ( |
Accuracy run | 50 tasks, 2026-10-12. Answering model |
Crawl4AI run | 20 different tasks, 2026-10-13, Crawl4AI 0.9.4. Crawl only, no LLM: did the crawler fetch the gold page? In the accuracy run, whether a system fetched the gold page closely tracked whether it answered correctly. |
Baselines | Claude picks every link: Claude reads each page and chooses the next link. Breadth-first: every page to depth 2. All systems share jevcrawl's fetcher and remove navigation and footers. |
Costs | Jev cost is billed and exact. LLM costs are token counts at list price. |
Caveats | 50 and 20 tasks are small samples: the cost gap is large and stable, the accuracy gap less so. Crawl4AI drives a real browser; the benchmark ran jevcrawl without JavaScript rendering. |
The benchmark code and how to reproduce every number: bench/.
Tools
Tool | Use it for |
| The answer may be several pages deep. Returns the relevant passages. |
| One known page. With a goal, only the relevant passages; without one, all content and no Jev call. |
| Ranked URLs only, to decide what to |
{"blocks": [{"url": "https://docs.python.org/3/whatsnew/3.8.html",
"heading_path": "What’s New In Python 3.8 > New Features > Assignment expressions",
"text": "There is new syntax := that assigns values to variables as part of a larger expression. ...",
"char_start": 307, "char_end": 580, "relevance": 0.97}],
"uncertain": [],
"pages": [{"url": "https://docs.python.org/3/", "title": "3.15.0 Documentation", "lang": "en",
"text_sha256": "b624ce93..."}],
"stats": {"pages_fetched": 6, "tokens_raw": 80034, "tokens_returned": 1873, "jev_calls": 11,
"cache_hits": 0, "jev_input_tokens": 164139, "jev_cost_usd": 0.0069,
"model": "jev-1.13.0", "degraded": false, "degraded_reason": null,
"needs_js_urls": [], "stopped_because": "answered", "elapsed_seconds": 8.3}}Trimmed to one block and one page. 80,034 tokens of pages became 1,873 tokens of answer, for $0.0069, in 8.3 seconds.
blocksare the relevant passages, best first;uncertainare borderline ones worth reading before concluding something is missing.char_start/char_endindex into the page text whose sha256 is inpages, so a quote can be checked later.stopped_becauseisanswered,frontier_empty,max_pages,max_seconds,budgetorauth_error.Expected failures come back as
{"error": "<code>", "message": "<what to do>"}; the tools never raise into the agent's session.
The same operations work from the command line (uv tool install jevcrawl or
pip install jevcrawl puts jevcrawl on your path):
jevcrawl crawl https://docs.python.org/3/ --goal "What does the walrus operator do?" [--json]
jevcrawl scrape https://example.com/help/returns [--goal "..."] [--json]
jevcrawl map https://example.com --goal "..." [--limit 20]
jevcrawl doctorHow it works
Jev decides, it does not write. Jev returns a probability per yes/no question, so it cannot produce text. Everything you get back is copied from the page.
Page text is data, never instructions. It travels only in data fields of a question; every question sentence is a fixed string written by jevcrawl. An injected "ignore previous instructions" can at worst bias a score.
Links below the priority floor are kept in reserve, not dropped, so a crawl never stops early while it still has page budget.
Failures degrade, they do not hide. After three failed Jev requests the crawl continues breadth-first, returns every block unscored in
uncertain, and setsstats.degraded.Repeats are free. Jev's answers are cached locally (
~/.jevcrawl/cache), so the same goal on the same pages costs nothing.
Configuration
Environment variable | Meaning | Default |
| Jev API key (required) | none |
| Jev-compatible endpoint |
|
| model id |
|
| answer cache |
|
| skip URLs the site's |
|
| render JavaScript-only pages when the |
|
Everything else (thresholds, links per page, concurrency) has a measured default in
jevcrawl/config.py, and Python callers can pass their own Config.
Cost and privacy
Jev charges for input tokens only, $0.042 per million for jev-1.13.0; in the benchmark a crawl
cost $0.011 on average. max_decision_usd is a hard limit: a request that could pass it is never
sent, and the crawl stops with budget. Every result reports jev_cost_usd, and tokens_raw
(what a plain crawl would hand your LLM) against tokens_returned.
Page text is sent to TypeSafe (blocks, link labels and URLs) to be scored. The API key is read from the environment and never written to disk or logs.
Crawling responsibly
jevcrawl follows each site's robots.txt by default (set JEVCRAWL_RESPECT_ROBOTS=0 to turn
that off) and does not rate-limit itself beyond fetching four pages at a time. You are
responsible for the sites you crawl, their terms of service and the law that applies to you. It
never logs in and does not try to get past paywalls or CAPTCHAs. Without the js extra it does
not run JavaScript (pages that need it are listed in stats.needs_js_urls). Every call is
bounded by max_pages, max_seconds, max_decision_usd and return_tokens.
Contributing
uv sync
uv run ruff check . && uv run ruff format --check . && uv run mypy --strict jevcrawl
uv run pytest # offline: all HTTP is mocked
TYPESAFE_API_KEY=... uv run pytest -m live -s # one tiny real request, a fraction of a centTests never touch the network or spend money; anything that does is marked live. The README
charts are drawn by assets/build.py from the measured numbers. Issues and
pull requests are welcome; please report security problems as described in
SECURITY.md.
Acknowledgments
WebWalkerQA by Alibaba's Tongyi Lab, for a benchmark of questions that really need crawling.
Crawl4AI, the baseline we measure against.
License
Available Tools
3 toolscrawlA
Crawl a website toward a goal and return only the relevant passages, verbatim.
Use this instead of fetching pages yourself when the answer may be several pages deep (documentation, help centers, policies, product catalogs). A small decision model scores every block of text and every link, follows the most promising links first, and stops as soon as the collected text answers the goal. No language model runs inside the crawl.
Args: goal: A specific question to answer, e.g. "How many days do I have to get a refund?". Vague goals ("learn about the shop") return vague results. start_url: Where to start; the crawl stays on this host and its subdomains. max_pages: Page limit. Start with 10-30. max_seconds: Wall-clock limit. return_tokens: Most text (estimated tokens) to hand back. max_decision_usd: Hard limit on what the decision model may cost for this call.
Returns:
blocks: relevant passages, best first. Each has url, heading_path, text
(copied from the page), char_start/char_end (offsets into the page text whose
sha256 is in pages) and relevance (0-1).
uncertain: borderline passages. Read these before concluding something is missing.
pages: every page fetched. stats: what it cost and why it stopped; check
stats.stopped_because ("answered", "frontier_empty", "max_pages", "max_seconds",
"budget") and stats.degraded (true means the decision model was unreachable and
results are unscored). stats.needs_js_urls lists pages that only render with
JavaScript and could not be rendered; stats.js_rendered_urls those it rendered.
On failure: {"error": code, "message": ...}.
Quote returned text together with its url.
Inputs: goal (required), start_url (required), max_pages, max_seconds, +2 more. Returns: object
mapA
Rank the URLs of a site by how likely they are to answer a goal, without returning content.
Runs the same best-first exploration as crawl but only scores links. Use it to decide
which pages deserve a scrape, or to see what a site offers on a topic.
Returns urls: the top limit entries, best first, each {url, priority, p_target, p_hub, anchor, found_on} (p_target: the page itself likely answers the goal; p_hub:
it likely leads to pages that do), plus pages (fetched) and stats. On failure:
{"error": code, "message": ...}.
Inputs: goal (required), start_url (required), limit, max_pages. Returns: object
scrapeA
Fetch one known page and return its text as blocks, optionally pruned to a goal.
Without goal: every block of the page that survives the free filters (navigation,
footers and repeated boilerplate removed), in page order, with no decision-model call.
With goal: only the blocks that help answer it (blocks) plus borderline ones
(uncertain); links are not followed.
Returns the same shape as crawl (blocks, uncertain, pages, stats), or
{"error": code, "message": ...}. Text is verbatim with char_start/char_end offsets.
Inputs: url (required), goal. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
TDQS
Scored across 3 tools
The three tools have clearly distinct roles: crawl performs multi-page goal-directed exploration, scrape fetches one known page, and map ranks URLs without returning content. Although crawl and scrape can both accept a goal, the descriptions make the boundary explicit through link-following and page scope.
All tool names are concise lowercase verbs in a consistent style: crawl, scrape, map. There is no mixing of naming conventions or verb forms.
Three tools is a tight, well-scoped set for a goal-directed web crawling server. Each tool handles a distinct phase: discovery (map), single-page extraction (scrape), and multi-page retrieval (crawl).
The surface covers the full retrieval lifecycle for the stated domain: ranking URLs to explore, fetching a known page, and crawling multiple pages toward a goal. No obvious operational gap remains for the advertised web-crawling purpose.
Maintenance
Related MCP Connectors
Live Google web search for AI agents that returns only the passages that answer the question.
Crawls any documentation or SaaS site and compiles a standards-compliant /llms.txt manifest and J.
Search the web, fetch any URL and map a site as clean, LLM-ready Markdown.
Crawl a website into clean Markdown for LLMs/RAG: main content only, linked docs converted.
Related MCP Servers
- AlicenseAqualityDmaintenanceFetches web content and returns relevant chunks using RAG with semantic chunking, query-based scoring, and LexRank, supporting PDFs and 50+ languages.1MIT
- FlicenseNot gradedqualityDmaintenanceCrawls a website, indexes its content, and answers questions with cited sources over MCP.-
- AlicenseNot gradedqualityBmaintenanceEnables agents to perform cheap web recall by discovering candidates via You.com and ranking them with TypeSafe Jev, returning a small scored URL list with prompt-injection risk flags over MCP and REST.2MIT
- AlicenseAqualityBmaintenanceEnables coding agents to perform web research by using TypeSafe JEV to semantically filter search results and parsed content blocks, returning compact evidence with provenance instead of dumping raw pages into the downstream model context.13Apache 2.0