Give your agent a PDF tool, not a PDF.

nupdf is a PDF engine for AI agents, in Rust. One binary reads, searches, renders, edits and redacts PDFs, and answers every command in JSON a model can act on.

See what it copies

Use nupdf for PDF work. Install it with `cargo install --locked nupdf-cli`. Run `nupdf guide` once to learn every command, or add it as an MCP server with `claude mcp add nupdf -- nupdf mcp`. Start with `nupdf info FILE`. Prefer `nupdf search` and `nupdf extract --max-chars` over reading whole files, and cite the ids it returns. More: https://nupdf.dev/llms.txt

150 / 150agent runs solved, over 30 tasks
8,988median tokens a task over MCP, the fewest of any tool tried
5,142files checked against MuPDF
0crashes in 1.5 million runs on PDFs from the open web
Page 15 of Free Culture by Lawrence Lessig, a page of body text
Free Culture, Lawrence Lessig, page 15 (CC BY-NC). Point at any line.
p15page
.b1block
.l3line
.s1span
:17-29characters

Every span of text has an address. A search hit returns it; any other command accepts it.

{
  "page": 15,
  "id": "p15.b1.l3.s1:17-29",
  "text": "free culture"
}
$ nupdf redact freeculture.pdf --id p15.b1.l3.s1:17-29

Up and running in a minute.

One install. Use it from a shell, hand it to an agent, or link it as a library.

$ cargo install --locked nupdf-cli
$ nupdf info report.pdf                        # triage: pages, text layer, scans, forms, what to run next
$ nupdf extract report.pdf --format markdown   # the text, as Markdown
$ nupdf search contract.pdf "terminat" --options ignore-case
$ nupdf redact statement.pdf -o clean.pdf --preset email --preset phone
$ nupdf verify clean.pdf --preset email --preset phone
$ cargo install --locked nupdf-cli
$ claude mcp add nupdf -- nupdf mcp            # one tool per command, each taking one file or many
$ cargo install --locked nupdf-cli
$ mkdir -p ~/.claude/skills/nupdf
$ nupdf guide --skill > ~/.claude/skills/nupdf/SKILL.md
# Cargo.toml
[dependencies]
nupdf = "0.1"

// main.rs
let doc = nupdf::Document::open(std::fs::read("report.pdf")?)?;

One binary. Read, change, prove.

Every command prints one versioned JSON report and exits with a code that says what happened.

01

Read

Text with an address for every span, within a budget, with a cursor to continue.

infoextractsearchtablesrenderocrformsannotssignaturesoutlinelinksattachmentsfontsimages
02

Change

Inputs are never modified. Every written file is read back before it is kept.

mergesplitpagesmetaformscleanflattensanitizeencryptdecryptbatch
03

Prove

Redaction that searches its own output, and a way to check any file or compare two.

redactverifydiffschemaguidemcp

Measured with agents, not adjectives.

Thirty graded tasks, five runs each, one small-model agent a run, each allowed its own tool only: 1,045 runs. The graders use pdftotext, qpdf and MuPDF, never nupdf.

How the agent handled the PDFsRuns passedTokensTime
nupdf, over MCP
150 / 1508,98813 s
nupdf, over the CLI
150 / 15014,57816 s
pymupdf4llm with PyMuPDF, in Python
147 / 15012,80523 s
pypdf and pdfplumber, in Python
145 / 15011,82321 s
PDF Oxide, in Python
108 / 15020,85830 s
PDF Oxide's MCP server
79 / 10515,43620 s
pymupdf4llm-mcp
38 / 1054,61723 s
The model reading page images
75 / 8517,40212 s

Tokens and wall time are medians over passed runs. Tools that cannot write a PDF skip the tasks that need one. Over the CLI, nupdf used slightly more tokens than the two Python arms. Tasks, graders and every run

Quick, and checked.

Each tool is asked for the text of every page, as an agent would ask: a new process for each call, start-up included. Measured on one Apple silicon Mac, an M5 Max.

One call the median over 200 test files; shorter is faster

ToolTime for one callPeak memoryRight text
nupdf
10.8 ms6.5 MB162 / 162
PDF Oxide pdf-oxide
11.9 ms10.8 MB157 / 162
MuPDF mutool
11.9 ms5.6 MB161 / 162
Poppler pdftotext
23.4 ms10.4 MB160 / 162
PDF Oxide Python
46.3 ms30.3 MB157 / 162
pypdf
83.8 ms37.7 MB156 / 162
PDFium pypdfium2
84.2 ms40.5 MB162 / 162
pdfminer.six
84.6 ms35.6 MB151 / 162
MuPDF PyMuPDF
84.7 ms55.6 MB162 / 162
pdfplumber
84.8 ms36.3 MB155 / 162

A long document pages a second over 17 documents of about 100 pages; longer is faster

ToolPages a secondPeak memoryCompleted
nupdf 8 threads
2,72932.0 MB30 / 30
nupdf
1,56813.0 MB30 / 30
Poppler pdftotext
1,07213.5 MB30 / 30
PDFium pypdfium2
75752.1 MB30 / 30
MuPDF mutool
49810.0 MB30 / 30
PDF Oxide pdf-oxide
34229.1 MB30 / 30
PDF Oxide Python
31847.5 MB30 / 30
MuPDF PyMuPDF
30559.3 MB30 / 30
pypdf
18844.7 MB21 / 30
pdfminer.six
10542.6 MB23 / 30
pdfplumber
6984.1 MB20 / 30

nupdf is not the best on every column: MuPDF's own tool uses the least memory in both tables, and on 8 of the 30 long documents pdftotext was quicker. "Right text" counts the files where a tool's words match PDFium's, out of those where MuPDF and PDFium agree. Times are over the files every tool completed. The files come from the pdf.js, PDFium, qpdf and veraPDF public test suites. nupdf 0.1.0, MuPDF 1.28.6 and PDF Oxide 0.3.78, run on 14 November 2026. Method and every other benchmark

99.88%agreement with MuPDF on the median of 97 checks
1.5Mruns on 100,000 web PDFs, malware included, without a crash
0unsafe; the workspace forbids it
19fuzz targets, one per parser surface

nupdf builds on MuPDF's work. It carries MuPDF's algorithms into safe Rust, credits Artifex in every derived file, and is checked against MuPDF file by file. All results

Open source, AGPL-3.0. Derived from MuPDF by Artifex Software, Inc.
DocsGitHubcrates.iollms.txt