# jurl as an agent's web tool: fewer tokens, all from the page

jurl mcp gives Claude Code, Claude Desktop or Cursor five web tools. On 34 pages they handed back a median 27.5 times fewer tokens than the page, every line from the page, and a plain "Not found" when the page doesn't say it.

2026-10-08 · https://jurl.dev/blog/every-token-on-the-page/

When an agent needs something from the web, it calls a tool, and whatever the tool returns goes into its context. jurl can be that tool:

```
claude mcp add jurl -- jurl mcp
```

Claude Desktop and Cursor take the same entry in their config files. The agent gets five tools, one for each thing jurl already does on the command line:

| Tool | Like | Returns |
| --- | --- | --- |
| `answer` | `--precise -q` | The exact answer, its paragraph, a link that highlights it |
| `read_page` | `jurl`, `-q` | The blocks that carry the page, or that answer `question` |
| `find_links` | `--links` | The links worth following |
| `find_code` | `--code` | Code blocks, as written |
| `find_image` | `--find`, `--image`, `--vision` | Image URLs |

Asked how much memory a Cloudflare Worker can use, `answer` returns this from Cloudflare's limits page:

```
128 MB

Each [isolate](https://developers.cloudflare.com/workers/reference/how-workers-works/#isolates) can consume up to 128 MB of memory, including the JavaScript heap and [WebAssembly](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) allocations. This limit is per-isolate, not per-invocation. A single isolate can handle many concurrent requests.

<https://developers.cloudflare.com/workers/platform/limits/#:~:text=consume%20up%20to-,128%20MB,-of%20memory%2C%20including>
```

496 characters. To find them, jurl's models read 18,680 tokens of the page. The agent reads the answer, the paragraph it's in, and a link to the line.

## 34 pages, one question each

Docs, API references, pricing pages, Wikipedia, GDPR, the IRS and GOV.UK, release notes, READMEs and news, measured on 7 October with jurl 0.1.11. For each page we counted the tokens the agent reads, checked whether the answer is in them, and checked every line against the page.

| | Tokens the agent reads (median) | Answer in it | Lines not on the page | Code lines not on the page |
| --- | --- | --- | --- | --- |
| Whole page as markdown | 8,178 | 34/34 | none | none |
| `read_page` | 230 | **34/34** (102 of 102 runs) | **0 of 1,632** | **0 of 315** |
| `answer` | **110** | 28/34 (79 of 102 runs) | **0 of 373** | **0 of 14** |
| Claude Code's WebFetch | 132 | 30/34, one wrong | 198 of 324 | 33 of 75 |

The automatic line check flagged 8 of jurl's lines; we looked each one up by hand, and all were Wikipedia text with a hidden span in the middle of it.

Against the whole page, `read_page` gave the agent a median 27.5 times fewer tokens, and the answer was there in every run.

Against WebFetch, jurl isn't shorter. WebFetch has a small model read the page and write a reply, and replies are short. The difference is whose words they are: 61% of WebFetch's lines and 44% of its code lines aren't on the page. Most of that is harmless framing, like "According to the pricing page…". Some isn't. On "What's New in Python 3.13" it gave `PCbuild/build.bat --experimental-jit`, the flag for the experimental JIT, as the Windows command for the free-threaded build.

When the page doesn't say it (two of the 34 questions), jurl exited with "not found" in all 12 runs, in one line of about 20 tokens. WebFetch said the page didn't cover it and then answered anyway, with 14 lines of `aiofiles` code, 11 of them not on the page.

## Where jurl does worse

- **`answer` misses.** It had the exact answer in 79 of 102 runs. 10 of the misses were right in other words from the page, which our strict check doesn't accept. And it's not deterministic: on 6 pages it answered in some runs and not in others.
- **Speed.** `read_page` took 0.5 to 1.1 s. Sites that serve their own markdown answer a plain fetch in 0.1 to 0.3 s.
- **Big tables.** When the answer is in a big table, jurl returns the table. On Everest that's 2,029 tokens, a 45-row list of peaks included.
- **Questions with long answers.** Every question here had one short answer. For "summarize this page" a handful of blocks won't do, and the whole page is the better input.

## "Not found" is an answer

When the page doesn't say it, the tool returns a normal result that starts with `Not found:`, the way the CLI exits 1. When something breaks (a 404, a bad key), it's a tool error, the way the CLI exits 2. The agent can tell "the page doesn't say" from "I couldn't read the page", and it acts on it. From a real Claude Code session:

```
tool_use: mcp__jurl__answer {"url": "docs.github.com/…/rate-limits-for-the-rest-api", "question": "primary rate limit for authenticated users"}
tool_result: Not found: no part of … is exactly the answer (closest: "5,000 requests per hour", p=0.36)
tool_use: mcp__jurl__answer {… "question": "rate limit authenticated users requests per hour", "follow": true}
tool_result: 5,000 requests per hour …
```

It reworded the question, turned on `follow` and got the answer, without being told to.

The server is about 250 lines of Rust with no new dependencies; the binary grew by 97 KB. The code is in [src/mcp.rs](https://github.com/rayoplateado/jurl/blob/main/src/mcp.rs) and the design notes in [pull request #40](https://github.com/rayoplateado/jurl/pull/40).
