Skip to main content
Glama

optical-read-mcp

Your agent reads files as text. That's expensive. Show it a picture instead.

license python mcp platforms

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 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.

The trick isn't mine. It's DeepSeek-OCR's contexts optical compression, popularized by Sean Goedecke's write-up. This just points it at the thing agents do all day long: reading files.

The gist

- 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.

Related MCP server: smart-context-mcp

Use it in Claude Code

The repo ships a project .mcp.json, so it's two steps:

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:

{
  "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.

NOTE

The modellooks 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 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 covers the rest.

Credits

Standing on the shoulders of:

Built by hyprcat · MIT

Available Tools

3 tools
ReadMassiveA

Token-efficient REPLACEMENT for the built-in Read/ReadFile tool — use this INSTEAD of Read whenever a file is large, or you're reading several files at once. It renders the file(s) to a compact, line-numbered image that a high-resolution vision model reads for ~7x fewer tokens than the raw text ("optical compression"). Same inputs as Read (a path or a list of paths); the returned PNG page(s) ARE the file's content.

<how_it_works>
Each page is a square image (kept small enough that the reader does NOT downscale it,
so the tiny font stays crisp) filled edge-to-edge with the file's characters — no
wasted whitespace. Whitespace is encoded as two tinted marker glyphs so it costs
almost nothing; you MUST decode them to recover the exact original text.
</how_it_works>

<symbols>
Each source line is shown as  ¶<number>│<code>  :
- red "¶"   = the START of a source line. (A visual row edge is NOT a line break —
              only "¶" is; a long line simply wraps onto the next row until the next "¶".)
- green "N│" = that line's LINE NUMBER, ending with "│". Use it to cite file:line.
- blue "→"  = exactly 4 SPACES of indentation. One "→" per 4 spaces ("→→" = 8); any
              leftover 1-3 spaces are literal.
- Every other glyph is the file's literal character.
RECONSTRUCT: split the stream on "¶"; in each piece the digits before "│" are the
line number and the rest is the code; replace each "→" with 4 spaces. BLANK lines are
omitted — a gap in the line numbers (e.g. 12 then 15) means those lines (13, 14) were
blank. Content flows left-to-right, top-to-bottom, wrapping to fill every row.
</symbols>

<how_to_read>
- VIEW the returned image pages DIRECTLY with your own vision — the same way your
  built-in Read / image-viewing tool shows you a PNG. You are a vision model; just
  LOOK at the pixels and read the text off them.
- DO NOT try to OCR the image with code, an image/vision library, a subprocess, or by
  decoding the base64 by hand. That defeats the entire purpose (it re-expands the
  content back into text tokens) and is slower and less accurate than simply looking.
  If your harness surfaces the page as a file path or attachment, open it with your
  existing Read/image tool — do not write a script to parse it.
- Treat the image as the file's ACTUAL content. Read the glyphs, decode the symbols
  above, and reason about the code/text exactly as if you had read the raw file — do
  NOT merely describe the picture.
- The header bar names the file and repeats this legend.
- Every line carries its own green "N│" line number, so you can cite file:line and
  plan edits directly from the image — you do NOT need a separate text read first.
</how_to_read>

<when_to_use>
CHOOSE ReadMassive when ALL hold:
  1. the reader is a HIGH-RESOLUTION vision model (Claude Fable 5, Opus 4.8, Sonnet 5)
     — it reads the dense square page at native resolution;
  2. the file is non-trivial (roughly >800 tokens) OR you are loading MANY files at
     once — that is where the token saving outweighs the image's fixed overhead.
Highest-value cases: reading/understanding or reviewing large source files, logs,
JSON, generated/minified code, lockfiles, docs; surveying a whole codebase in one
call; long sessions that would otherwise exhaust the context window. Line numbers are
included, so this is fine to use even when you intend to edit afterwards.

PREFER A NORMAL TEXT READ when ANY hold:
  - the file is small (a few hundred tokens) — the image costs MORE than the text
    (the summary says "NOTE: ... CHEAPER" when this happens);
  - the reader is GPT-5.6 Sol or any model that downscales images to a small short
    edge (it may misread this density), or a non-vision model.
</when_to_use>

<optimal_strategy>
- Pass MANY paths in ONE call to survey a codebase cheaply — files render concurrently
  and are cached by path+mtime, so unchanged re-reads are free.
- Call ReadMassiveEstimate first when unsure whether a specific file is worth imaging.
- If a summary says "TRUNCATED", raise max_pages_per_file or split the file — content
  past the cap is NOT shown, never silently guessed.
</optimal_strategy>

Args:
    paths: A single file path or a list of file paths.
    max_pages_per_file: Safety cap on image pages per file; truncation past this is
        reported, never silent.
    include_summary: Prepend a one-line token/cost summary before each file.

Returns:
    Interleaved text summaries and PNG image content, in path order.
ParametersJSON Schema
NameRequiredDescriptionDefault
pathsYes
include_summaryNo
max_pages_per_fileNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so thoroughly. It explains the image-based output, the symbol encoding, blank-line omission, line wrapping, truncation reporting, caching by path+mtime, and concurrent rendering, far exceeding a basic read-only statement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but highly structured, with a front-loaded summary followed by clearly tagged sections: how_it_works, symbols, how_to_read, when_to_use, and optimal_strategy. Every section contributes necessary operational detail, especially the symbol decoding rules and the admonition to view the image directly rather than OCR it. There is no redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given three parameters, no output schema, and a complex image-based return format, the description is remarkably complete. It covers the return value, how to interpret the image, symbol reconstruction, line-number citation, truncation behavior, caching, cost estimation, and model compatibility caveats. An agent has everything needed to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the JSON schema provides no field descriptions, the 'Args' section documents all three parameters: paths accepts a single path or list, max_pages_per_file is a truncation safety cap that reports rather than silently omits content, and include_summary controls a cost summary prefix. This fully compensates for the 0% schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens by naming ReadMassive as a token-efficient replacement for the built-in Read/ReadFile tool and states its exact behavior: rendering files to compact, line-numbered PNG images for a high-resolution vision model. It clearly identifies the resource (file paths) and action (read/render), and positions itself against the built-in Read tool and the ReadMassiveEstimate sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'when_to_use' section provides explicit 'CHOOSE ReadMassive when ALL hold' and 'PREFER A NORMAL TEXT READ when ANY hold' criteria, including model type, file size, and multi-file loading. It also names ReadMassiveEstimate as a pre-check and lists high-value use cases, giving concrete guidance on when to use this tool versus alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ReadMassiveEstimateA

Report text-vs-image token counts, compression ratio, and dollar cost per model — WITHOUT returning the images.

<instructions>
- USE THIS FIRST to decide whether reading a file as a packed image is worth it,
  before spending context on the pixels via ReadMassive.
- Reports per reader model: Claude Fable 5 (high-res vision), GPT-5.6 Sol (tiling),
  and the aspirational DeepSeek-OCR optical encoder — plus estimated input-token cost.
- A `compression_ratio` > 1 means the image is cheaper than the text; < 1 means a
  plain text read is cheaper (tiny files) — in that case just read the file normally.
</instructions>

Args:
    paths: A single file path or a list of file paths.

Returns:
    A JSON string: per-file token counts, ratio, and cost per model.
ParametersJSON Schema
NameRequiredDescriptionDefault
pathsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that images are NOT returned, that the tool reports per-model estimates, and includes the cost. It explains the compression_ratio semantics. However, it does not mention error behavior, invalid-path handling, or any limitations, which keeps it just shy of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with clear sections and uses a concise, front-loaded summary. The instructions are purposeful and not redundant. It is slightly longer than necessary but every part adds value, justifying a 4 rather than a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple schema (1 param) and the presence of an output schema, the description covers the tool's purpose, usage, return format, and interpretation well. Minor omissions like error cases and concrete examples prevent a 5, but the description is complete enough for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides only a name and type for 'paths' (string or array). The description adds 'A single file path or a list of file paths,' which clarifies the semantic meaning slightly but does not specify path formats, file type restrictions, or how paths relate to the estimation output. For a single trivial parameter, this is adequate but not rich.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool's function: 'Report text-vs-image token counts, compression ratio, and dollar cost per model — WITHOUT returning the images.' The verb 'Report' and the resource (token counts/cost) are specific, and it clearly distinguishes itself from ReadMassive by noting it avoids returning pixels.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: 'USE THIS FIRST to decide whether reading a file as a packed image is worth it, before spending context on the pixels via ReadMassive.' It also gives a clear exclusion: if compression_ratio < 1, 'just read the file normally.' This names the alternative tool and the decision rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ReadMassiveTextA

Render an arbitrary text blob to densely-packed base64 PNG image(s) — optical compression for text you already have in context rather than a file on disk.

<instructions>
- USE THIS to compress bulky text you are about to keep in context: a long tool
  output, pasted logs, a fetched document, a big diff. A high-res vision model
  re-reads the image for far fewer tokens than the raw text.
- VIEW the returned image DIRECTLY with your own vision (like your built-in Read /
  image tool). DO NOT OCR it with code, a library, or base64 decoding — just look at
  the pixels; anything else re-expands it back into text tokens and defeats the point.
- PACKED, line-numbered layout: each line is "¶N│code" — red ¶ starts a line, green N│
  is its line number, blue "→" = 4 spaces of indentation. Decode them to reconstruct
  the exact text (split on ¶; digits before │ are the line number; → -> 4 spaces).
  Treat the image as the text's ACTUAL content, not a picture to describe.
- Prefer ReadMassive when the content is a file on disk (adds caching + a filename
  header); use this only for in-memory text.
- If the summary says "TRUNCATED", raise `max_pages` — content past the cap is NOT shown.
</instructions>

Args:
    text: The text to render.
    title: Label drawn in each page's header band (for grounding).
    max_pages: Safety cap on pages; truncation is reported, never silent.
    include_summary: Prepend a token/cost summary.
ParametersJSON Schema
NameRequiredDescriptionDefault
textYes
titleNotext
max_pagesNo
include_summaryNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses the tool's behavior: it returns an image with a specific packed, line-numbered layout (explaining ¶, N│, and →), warns against OCR, notes that truncation is reported never silent, and explains the summary option. It also instructs the agent to read the image directly as actual content, covering both output format and consumption method.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose in the first sentence, then provides terse, high-value instructions in the <instructions> block. Every sentence serves a purpose — when to use, output decoding, sibling distinction, truncation handling, and the Args block adds parameter semantics without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of an output schema, the description thoroughly explains the return format (base64 PNG images), the visual layout and decoding rules, truncation reporting, and the optional summary. Along with default values in the input schema, the description leaves no significant gaps for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no property descriptions (0% coverage), so the description's Args block carries the full explanatory burden. It adds meaningful per-parameter semantics: text is 'the text to render', title is 'a label drawn in each page's header band', max_pages is 'safety cap on pages; truncation is reported, never silent', and include_summary 'prepends a token/cost summary'. This fully compensates for the missing schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Render an arbitrary text blob to densely-packed base64 PNG image(s)') and immediately distinguishes from the sibling ReadMassive ('use this only for in-memory text', 'Prefer ReadMassive when the content is a file on disk'). This makes the tool's purpose unambiguous and sets it apart from ReadMassive and ReadMassiveEstimate.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The <instructions> block explicitly states when to use the tool ('compress bulky text you are about to keep in context') and provides direct alternatives: 'Prefer ReadMassive when the content is a file on disk; use this only for in-memory text.' It also includes operational guidance about truncation and how to handle it (raise max_pages), satisfying both when-to-use and when-not-to-use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedReadMassive
    • First observedReadMassiveEstimate
    • First observedReadMassiveText

TDQS

A4.7/5.0

Scored across 3 tools

Disambiguation5/5

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.

Naming Consistency5/5

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).

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server that reduces AI agent token usage by up to 90% through intelligent context compression. Enables efficient code exploration, multi-file refactoring, and debugging by providing tools for smart reading, searching, and managing code context.
    4
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server that enables LLMs to understand images without native vision by converting image regions into text encodings (ASCII art, grayscale grids, color stats) and supporting progressive zoom, OCR, and overview summaries. Users can load images, get chunk overviews, crop and encode specific regions, and extract text using normalized coordinates.
    -