optical-read-mcp
<div align="center">
# optical-read-mcp
### Your agent reads files as text. That's expensive. Show it a picture instead.




</div>
Loading a big file into an agent burns thousands of tokens — most of them spent
on whitespace and boilerplate the model barely needs at full fidelity. But a
model doesn't _have_ to read text. Show it an **image** of the file and it reads
with its vision encoder instead, where a single token is worth roughly ten text
tokens.
`optical-read-mcp` is a small [MCP](https://modelcontextprotocol.io) server that
does exactly that. Hand it a path; it hands back a dense, line-numbered picture of
the file; and a high-resolution vision model — Claude Fable 5, Opus 4.8, Sonnet 5,
GPT-5.6 Sol — reads roughly **7× more code per token**.
<div align="center">
<img src="docs/density-comparison.png" alt="the same file, as text vs. as a packed image" width="900">
</div>
The trick isn't mine. It's DeepSeek-OCR's
[_contexts optical compression_](https://arxiv.org/abs/2510.18234), popularized by
Sean Goedecke's [write-up](https://www.seangoedecke.com/text-tokens-as-image-tokens/).
This just points it at the thing agents do all day long: reading files.
## The gist
```diff
- Read("app/router.py") → ~8,000 tokens of text in your context
+ ReadMassive("app/router.py") → one small PNG the model reads for ~1,100
```
Same shape as the Read tool you already use — a path, or a list of paths. What
comes back is a picture the model treats as the file's actual contents, line
numbers and all.
## Use it in Claude Code
The repo ships a project `.mcp.json`, so it's two steps:
```bash
uv sync
```
Open the folder in Claude Code, approve the **optical-read** server when it asks
(`/mcp` to check), and you're set. Ask it to read something big and it'll reach for
`ReadMassive` on its own.
Any other MCP client:
```json
{
"mcpServers": {
"optical-read": {
"command": "uv",
"args": ["run", "optical-read-mcp"]
}
}
}
```
## What the picture looks like
Text is packed edge-to-edge into a square — no wasted margins — and every source
line is written as `¶N│code`:
| | means |
| :----: | ----- |
| red **`¶`** | the start of a line |
| green **`N│`** | its line number, so the model can still tell you the bug's on line 214 |
| blue **`→`** | four spaces of indentation |
Blank lines are dropped; a jump in the numbers (12 → 15) brings them back. Nothing
is lost — the exact source is recoverable, and the test suite checks that on every
run. Full spec in [docs/FORMAT.md](docs/FORMAT.md).
> [!NOTE]
> The model _looks_ at the image. It should never OCR it with code — that would
> just turn the pixels back into the text tokens you were trying to avoid.
One rule makes or breaks this: **pages stay square and under 1560px.** Vision
pipelines quietly downscale anything larger, and that downscale smears a 5px glyph
into mush. Keep both sides small and the model reads it crisp and native.
## Does it really save 10×?
No, and it won't pretend to. DeepSeek's headline number is measured inside _its
own_ OCR encoder. What you actually save depends on how your reading model counts
image tokens, so every read reports the real figure:
| reading model | how it sees images | what you save |
| --- | --- | --- |
| **Claude Fable 5** · Opus 4.8 · Sonnet 5 | native, up to 2576px | **~7× on real code** |
| **GPT-5.6 Sol** | downscales to a 768px short edge | works, but less |
| DeepSeek-OCR | its own 16× compressor | ~10× (the dream) |
On a small file it's a _loss_ — the image has a fixed overhead a few hundred
tokens can't beat — and the tool says so and points you back to plain `Read`. This
earns its keep on large files, and on reading a whole pile of them at once.
## The three tools
- **`ReadMassive(paths)`** — the main event. One path or many, cached by mtime,
rendered in parallel.
- **`ReadMassiveText(text)`** — same idea for a blob you already have in hand: a
giant tool output, pasted logs, a fetched doc.
- **`ReadMassiveEstimate(paths)`** — just the token and cost math, no pixels, for
when you'd rather decide before committing the context.
## Under the hood
Pure Python: [Pillow](https://python-pillow.github.io) for the rendering, the
official MCP SDK for the server. A monospace font is bundled, so it renders
identically on macOS, Linux and Windows with nothing to install. The whole thing
is four small files, and the tuning knobs live at the top of `render.py`.
```
src/optical_read_mcp/
server.py the MCP tools
render.py text → packed, square, line-numbered PNG ← the interesting bit
tokens.py per-model token & dollar math
fonts.py finding a monospace font, anywhere
```
`uv run --with pytest pytest` runs the tests; [CONTRIBUTING.md](CONTRIBUTING.md)
covers the rest.
## Credits
Standing on the shoulders of:
- DeepSeek-OCR — [_Contexts Optical Compression_](https://arxiv.org/abs/2510.18234)
- Sean Goedecke — [_Should LLMs just treat text content as an image?_](https://www.seangoedecke.com/text-tokens-as-image-tokens/)
- George Mandis — [speeding up audio to pay OpenAI less](https://george.mand.is/2025/06/openai-charges-by-the-minute-so-make-the-minutes-shorter/), the same idea in a different key
<div align="center">
Built by **hyprcat** · [MIT](LICENSE)
</div>
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: ReadMassive reads files, ReadMassiveText renders arbitrary text, and ReadMassiveEstimate provides cost/compression estimates. Despite the shared ReadMassive prefix, the descriptions clearly separate file-based, text-based, and estimation workflows, leaving no ambiguity.
All tool names share the consistent 'ReadMassive' prefix with descriptive suffixes, forming a predictable pattern. The camelCase style is uniform and clearly conveys the tool's role (read, text, estimate).
Three tools is an ideal scope for a focused optical-reading server: one for file reading, one for in-memory text, and one for cost estimation. Each tool earns its place, and the count is neither too thin nor excessive.
The core reading workflow is well covered (files, text, and estimation for files). A minor gap is that ReadMassiveEstimate only accepts paths, not arbitrary text, so users cannot estimate costs for non-file content before using ReadMassiveText. Overall, the surface is nearly complete for its stated purpose.