Skip to main content
Glama
KayZedd

pdfops - Fast PDF tools for AI agents.

65x

78x

10x

7 ms

faster than pdftoppm at rendering 20 pages

faster than PyMuPDF at reading the tables of 50 pages

faster than pdftotext at the text of 308 pages

to open a file and say what is in it

One document on one machine, start-up included. Every row, the one pdfops loses too, is in the benchmarks.

Why pdfops

  • Built for agents. Every command returns one JSON document, errors say what to do next, and text can be read under a character budget with a resume point.

  • Everything in one place. Reading, layout, tables, OCR, rendering, page surgery, stamping, redaction, annotations, signatures, forms, encryption, inspection and creation: 31 tools behind one schema.

  • Nothing to set up. A single binary with no PDF libraries, no Python and no runtime. Only OCR needs an extra program, and pdfops can fetch the language data itself.

  • Fast. Files open in milliseconds whatever their size, and page work runs on all cores. See the benchmarks.

  • Redaction you can trust. Content is deleted from the page, not covered, and the result is checked by a second interpreter before anything is written.

  • Safe on files you did not write. Every command runs under a memory cap and a time limit, the MCP server runs each call in a process of its own and can be confined to one directory, and every command is exercised against a corpus of 988 hostile and malformed PDFs in CI.

  • Knows what a file is up to. scan reports scripts, actions that fire by themselves, attached programs, disguised names and text that is extracted but cannot be seen, the carrier of prompt injection. sanitize removes the active content and proves it by scanning the result.

Related MCP server: go-docs-mcp

Quick start

As an MCP server, with nothing installed beforehand:

Install in Cursor Install in VS Code

Client

How

Claude Code

claude mcp add pdfops -- npx -y pdfops-cli mcp

Codex

codex mcp add pdfops -- npx -y pdfops-cli mcp

VS Code

code --add-mcp '{"name":"pdfops","command":"npx","args":["-y","pdfops-cli","mcp"]}'

Cursor

the JSON below in ~/.cursor/mcp.json

Claude Desktop

the JSON below in claude_desktop_config.json (Settings, Developer, Edit Config)

Windsurf

the JSON below in ~/.codeium/windsurf/mcp_config.json

{
  "mcpServers": {
    "pdfops": { "command": "npx", "args": ["-y", "pdfops-cli", "mcp"] }
  }
}

Tools are named pdf_info, pdf_text, pdf_redact and so on, and take the same arguments as the CLI. Relative paths resolve against the server's working directory.

To keep an agent inside one folder, add --root: every path outside it is refused, including paths that arrive inside a document, and relative paths resolve against it.

{ "command": "npx", "args": ["-y", "pdfops-cli", "mcp", "--root", "/home/me/documents"] }

Install

Method

Command

Needs

npm

npm install -g pdfops-cli or npx pdfops-cli

Node 18+

Prebuilt, via cargo

cargo binstall pdfops

cargo-binstall

From source

cargo install pdfops

Rust 1.92+

Docker

docker run --rm -i -v "$PWD:/work" ghcr.io/kayzedd/pdfops mcp --root /work

Docker

Manual

download an archive and put pdfops on your PATH

nothing

The npm package is called pdfops-cli and installs the pdfops command. It is a small launcher: on first run it downloads the binary for your platform from the GitHub release, checks it against the published SHA-256 sum and caches it. Prebuilt binaries cover Linux (glibc and musl) and macOS on x86-64 and ARM64, and Windows on x86-64. The Docker image is Alpine with pdfops and tesseract with English; add -v for the folder to work in.

OCR additionally needs the tesseract program; nothing else does. Language data is fetched on request, without administrator rights:

pdfops ocr-langs                     # is tesseract there, which languages can be used
pdfops ocr-install --lang pol+eng    # download language data into the user's data directory
pdfops ocr-install --engine          # install tesseract itself where that needs no password

Tools

Command

What it does

Read

info

Page count, page size, metadata, encryption, outline and form summary

text

Text page by page, with a character budget and optional OCR fallback

search

Find text or a regex, returns pages and snippets

layout

Bounding box, font and size of every line or word

tables

Tables as rows of cells, Markdown or CSV

outline

Bookmarks with target pages

annotations

Highlights, comments, links and other markup, with positions

signatures

Digital signatures: who signed, and whether the document changed since

render

Pages to PNG, to look at charts, scans and layout

images

The images drawn on pages, as files

ocr

Recognised text of scanned pages, optionally written into a searchable copy

scan

Scripts, automatic actions, attachments, disguised content and hidden text, by severity

Build

create

A new PDF from Markdown

merge

Several PDFs into one

pages

Keep, reorder, duplicate or delete pages

split

Split by page count or by ranges

Edit

rotate

Rotate pages by multiples of 90 degrees

stamp

Watermark, header, footer, page numbers, text at a point or under a found text, an image such as a signature, or a QR code

annotate

Add a highlight, underline, strike-out, box, note or link

replace

Replace text in place, in the document's own font where possible

redact

Remove text, images and drawings in areas or matching text, then verify

set-meta

Title, author, subject, keywords, creator

compress

Shrink: lossless by default, optionally re-encoding and downscaling images

Forms

forms

Fields with their types, values and options

fill

Fill fields by name

Protect

encrypt

AES-256 passwords and permissions

decrypt

Remove password protection

sign

Sign digitally with a certificate, keeping earlier signatures valid

sanitize

Remove scripts, risky actions, attachments, XFA and media, then verify by scanning

Setup

ocr-langs

Whether tesseract is installed and which languages are usable

ocr-install

Download OCR language data, optionally install tesseract

Run pdfops <command> --help for the options of each.

Examples

$ pdfops info manual.pdf
{"encrypted":false,"file":"manual.pdf","form_fields":0,
 "metadata":{"created":"2026-06-30T09:07:46+00:00","producer":"GPL Ghostscript 10.07.1"},
 "outline_entries":645,"page_size_pt":{"height":792.0,"width":595.0},"pages":357,"pdf_version":"1.3",
 "size_bytes":1386723,"uniform_page_size":true}

$ pdfops search manual.pdf "calling convention" --max-results 1 --context 40
{"file":"manual.pdf","matches":[{"match":"Calling Convention","page":12,
 "snippet":"... 10.5.1 The Pascal Calling Convention ..."}],"query":"calling convention",
 "total_matches":17,"unreadable_pages":[]}

Reading:

pdfops text manual.pdf --pages 12- --max-chars 4000     # resume_at_page says where to continue
pdfops text manual.pdf --pages 14- --from-char 4000 --max-chars 4000   # on, inside a page that was cut
pdfops text scan.pdf --ocr --ocr-lang pol+eng           # OCR only the pages that have no text
pdfops tables report.pdf --pages 4 --format markdown
pdfops layout report.pdf --pages 4 --level words        # bbox, font and size per word
pdfops render report.pdf --pages 1-3 --dpi 150 -o out/  # look at charts and layout
pdfops --stream ocr scan.pdf --lang pol                 # a line per page as it finishes
pdfops ocr scan.pdf --lang pol -o searchable.pdf        # the same file, with text to search
pdfops images report.pdf -o images/

Building and page work:

pdfops create notes.md -o notes.pdf
pdfops merge a.pdf b.pdf -o merged.pdf
pdfops pages in.pdf --keep "3,1,5-" -o out.pdf
pdfops split in.pdf --every 10 -o parts/

Editing:

pdfops stamp in.pdf --text "Confidential · {page}/{pages}" --position footer -o out.pdf
pdfops stamp in.pdf --image signature.png --x 380 --y 690 --width 140 --pages last -o out.pdf
pdfops stamp in.pdf --text "Paid 2026-10-08" --x 72 --y 540 --size 11 -o out.pdf
pdfops stamp in.pdf --text "Paid 2026-10-08" --below "Total due" -o out.pdf   # a line under that text
pdfops stamp in.pdf --qr "https://example.com/doc/42" --anchor bottom-right -o out.pdf
pdfops redact in.pdf --text "Jan Kowalski" --text "\d{11}" --regex -o redacted.pdf
pdfops redact in.pdf --rect "2:100,200,300,220" -o redacted.pdf
pdfops replace in.pdf --find "2025" --with "2026" -o out.pdf
pdfops replace in.pdf --find "2025" --with "2026" --dry-run -o out.pdf   # the plan, nothing written
pdfops compress in.pdf --max-image-edge 1600 --image-quality 70 -o small.pdf

Forms and protection:

pdfops forms form.pdf
pdfops fill form.pdf --set name="Ada Lovelace" --set agree=true -o filled.pdf
pdfops encrypt in.pdf --owner-password secret --deny-copy -o locked.pdf
pdfops sign in.pdf --p12 identity.p12 --p12-password secret --reason "Approved" -o signed.pdf
pdfops sign in.pdf --cert me.crt --key me.key --visible "1:360,700,560,760" -o signed.pdf
pdfops signatures signed.pdf                            # valid, unchanged, who and when
pdfops signatures signed.pdf --trust company-root.pem   # and whether the signer is one of yours
pdfops sign in.pdf --p12 identity.p12 --tsa http://timestamp.digicert.com -o signed.pdf
pdfops scan inbox/offer.pdf                             # what is in it, before reading it
pdfops sanitize inbox/offer.pdf -o offer-clean.pdf      # scripts, actions, attachments removed

Markup:

pdfops annotate in.pdf --text "liability" --comment "check with legal" -o marked.pdf
pdfops annotate in.pdf --kind link --rect "1:72,50,300,70" --url https://example.com -o out.pdf
pdfops annotations marked.pdf

Conventions

  • Output. Every command prints one JSON document on stdout. Errors go to stderr as {"error": "..."} with exit status 1. Add --pretty to indent.

  • Progress. With --stream, ocr, text --ocr, render, images, split, redact and replace print one line of JSON per finished page or file, {"event":"progress","step":"render","done":7,"total":20,"page":12,...}, and the usual result as the last line. Pages are worked on in parallel, so events come in the order the work finishes; each names its page and done rises by one per line. Over MCP the same events arrive as notifications/progress when the call carries a progress token.

  • Writing. Commands that write take -o. It may be the input file: output goes through a temporary file.

  • Pages are 1-based and comma separated: 3, 2-5, 7- (to the end), -4 (from the start), 5-2 (descending), last, odd, even, all.

  • Positions are in points with the origin at the top-left corner of the page as displayed, y growing downwards. layout reports them, stamp --x/--y and redact --rect accept them, and a pixel of render --dpi 72 is exactly one point.

How it compares

pdfops

PyMuPDF

pypdf

pdfplumber

qpdf

poppler-utils

Text extraction

✓

✓

✓

✓

–

✓

Word positions and fonts

✓

✓

–

✓

–

positions

Tables

✓

✓

–

✓

–

–

Render pages

✓

✓

–

✓

–

✓

OCR

✓

✓

–

–

–

–

Merge, split, reorder, rotate

✓

✓

✓

–

✓

partly

Text, image and QR stamps

✓

✓

by overlay

–

by overlay

–

Redaction that removes content

✓

✓

–

–

–

–

Redaction verified before writing

✓

–

–

–

–

–

Replace text in place

✓

–

–

–

–

–

Fill forms

✓

✓

✓

–

–

–

Encrypt and decrypt

✓

✓

✓

–

✓

–

Add and list annotations

✓

✓

✓

list

–

–

Sign digitally

✓

–

–

–

–

–

Verify signatures

✓

–

–

–

–

✓

Inspect for active content and hidden text

✓

–

–

–

–

–

Remove active content

✓

✓

–

–

–

–

Memory and time limits per call

✓

–

–

–

–

–

Create from Markdown

✓

from HTML

–

–

–

–

Built-in MCP server and tool schemas

✓

–

–

–

–

–

JSON from every command

✓

library

library

library

partly

–

Runtime needed

none

Python

Python

Python

none

none

License

MIT

AGPL or commercial

BSD

MIT

Apache-2.0

GPL

PyMuPDF is the closest in scope and is an excellent library; it is written in C, needs Python, and its AGPL license matters if you ship it. pdfops trades some breadth for a single MIT-licensed binary whose tools an agent can call directly.

Benchmarks

Best of 3 whole-process runs, start-up included, since that is what one tool call costs an agent. Document: the NASM 2.16 manual, 308 pages and 1.2 MB; images on a 352 page, 2.6 MB book with pictures; forms on a one page form. Machine: 8 core AMD Ryzen 7 9800X3D, Windows 11. Versions: poppler 25.07, qpdf 12.4, PyMuPDF 1.28, pypdf 6.19, pdfplumber 0.11, pyHanko 0.37 (CLI 0.5), tesseract 5.5. The fastest entry of each row is bold; n/a marks a tool that was installed and did not complete the task. PyMuPDF's entry for sanitize is its scrub.

Task

pdfops

command line tool

PyMuPDF

pypdf

pdfplumber

text, all pages

53 ms

pdftotext 540 ms

297 ms

1273 ms

11.3 s

tables, 50 pages

20 ms

-

1558 ms

-

1629 ms

render, 20 pages at 150 dpi

41 ms

pdftoppm 2677 ms

593 ms

-

-

ocr, one page

1087 ms

tesseract 759 ms

-

-

-

merge, three copies

57 ms

qpdf 204 ms

401 ms

2037 ms

-

split, one file per page

156 ms

pdfseparate 46.0 s

437 ms

2513 ms

-

redact, a word on every page

564 ms

-

1926 ms

-

-

sign, RSA-2048

23 ms

pyhanko 615 ms

-

-

-

Task

pdfops

command line tool

PyMuPDF

pypdf

pdfplumber

info

7 ms

pdfinfo 12 ms

129 ms

203 ms

222 ms

text, all pages

53 ms

pdftotext 540 ms

297 ms

1273 ms

11.3 s

search, all pages

53 ms

-

326 ms

-

-

layout, every word with its box

194 ms

-

346 ms

-

10.9 s

tables, 50 pages

20 ms

-

1558 ms

-

1629 ms

outline

15 ms

-

128 ms

221 ms

-

render, 20 pages at 150 dpi

41 ms

pdftoppm 2677 ms

593 ms

-

-

ocr, one page

1087 ms

tesseract 759 ms

-

-

-

images, all embedded images

230 ms

pdfimages 5621 ms

1297 ms

1553 ms

-

create, 100 sections of Markdown

12 ms

-

-

-

-

merge, three copies

57 ms

qpdf 204 ms

401 ms

2037 ms

-

pages, keep 10

17 ms

qpdf 138 ms

138 ms

285 ms

-

split, one file per page

156 ms

pdfseparate 46.0 s

437 ms

2513 ms

-

rotate, all pages

20 ms

qpdf 144 ms

147 ms

769 ms

-

stamp, text on every page

21 ms

-

279 ms

-

-

stamp, QR code on every page

121 ms

-

-

-

-

annotations, list

18 ms

-

227 ms

-

309 ms

annotate, highlight a word on every page

291 ms

-

700 ms

-

-

redact, a word on every page

564 ms

-

1926 ms

-

-

replace, a word on every page

575 ms

-

-

-

-

scan, structure and hidden text

232 ms

-

-

-

-

scan, structure only

16 ms

-

-

-

-

sanitize

32 ms

-

2072 ms

-

-

set-meta

19 ms

-

140 ms

771 ms

-

compress

31 ms

qpdf 179 ms

378 ms

-

-

encrypt, AES-256

25 ms

qpdf 174 ms

150 ms

825 ms

-

decrypt

38 ms

qpdf 170 ms

158 ms

875 ms

-

sign, RSA-2048

23 ms

pyhanko 615 ms

-

-

-

signatures, verify

16 ms

pdfsig n/a

-

-

-

forms, list fields

7 ms

-

121 ms

169 ms

-

fill, one field

9 ms

-

130 ms

198 ms

-

Reproduce with scripts/bench.py on any document.

Why it is fast: info, layout, tables, render, images and ocr read objects on demand, so opening a file costs a few milliseconds whatever its size. Text extraction, layout, tables, rendering, splitting, image export and OCR run on all cores. Page operations copy only the objects the selected pages reach, so output size and time follow the selection, not the source.

Using it without MCP

pdfops tools prints [{"name", "description", "inputSchema"}] for every command. Pass these to any function calling API, then run the call through the CLI or the library.

let result = pdfops::tools::call(
    "pdf_text",
    serde_json::json!({"input": "report.pdf", "pages": "1-3"}),
)?;

Typed entry points live in pdfops::ops, for example pdfops::ops::read::text(TextArgs { .. }).

Behaviour worth knowing

pdfops <command> --help has the detail for each command.

  • Any script. Text that stamp, fill, replace, create and ocr -o draw is shaped and embedded as font subsets; where no one font has every character, several share the text. Hebrew and Arabic are laid out from the right, Indic scripts and Thai are formed into their clusters, and all of it reads back as it was written, so a word is found by typing it.

  • Replace writes in the document's own font where it has the glyphs and moves the rest of the line along; values of text fields and comments of annotations are changed too. A line that grows past its column passes its last words on to the next line, or is drawn narrower where it cannot. In a table all of this stays within the cell.

  • Dry run. redact, replace, annotate and stamp take --dry-run: all the work, nothing written.

  • Reading in parts. text --max-chars stops at the budget and returns resume_at_page and resume_at_char. Give them back as --pages N- and --from-char, and reading goes on where it stopped, also in the middle of a page longer than the budget.

  • Stamp places text, an image or a QR code at a point (--x, --y), or under or over the first match of a text on each page (--below, --above, --gap), so a line can be added without measuring. A line break in the text starts a new line.

  • Scan reports scripts, actions, attachments, disguised content and hidden text by severity, and with --clamav what ClamAV's signatures recognise, where that is installed. Sanitize removes the active content and scans the result before writing it.

  • Signatures. sign appends, so earlier signatures stay valid. It can show the signature on a page (--visible) and embed a timestamp authority's statement of the time (--tsa). signatures checks that the bytes are unchanged and who signed; with --trust it checks the signer's chain against certificates you name, and with --revocation their revocation lists. Any other change to a signed PDF invalidates its signatures, as it must.

  • Merging and page work keep bookmarks and links that lead to pages in the output, and rename the form fields of later inputs doc2.<name> so equal names do not share a value.

  • Damaged files are repaired for reading and rebuilt for writing, encrypted ones too; the result then carries repaired_inputs. Protected files stay protected when edited; only decrypt removes the protection.

Limits

  • Tables without ruling lines are inferred from how their text lines up, and say so (detected_by: alignment). An inference deserves a look.

  • Replace draws a line no narrower than 70% of its width. What a replacement still runs over by is in overflow_pt, and --dry-run shows it before anything is written. It does not add lines: a replacement with a line break is refused.

  • Stamp draws over the page and moves nothing aside. Placed by a found text, it reports its box and how many words lie under it (words_under_it).

  • Create follows the styling of HTML for how text looks: colour, background, bold, italic, underline, strike-through, size and alignment, from style attributes and <style> rules that go by tag, class and id. Layout is not read: no boxes, floats or grids.

  • OCR is tesseract's, in quality and in languages.

  • Scan is not a verdict: it never calls a file safe, and lists what it did not check.

  • Resources. A command may use 4 GiB and 300 seconds by default: --max-memory, --timeout, or PDFOPS_MAX_MEMORY and PDFOPS_TIMEOUT; 0 lifts a limit. Rasters are capped at 64 megapixels.

Development

cargo test                                # under a second; fixtures are generated in memory
cargo test -- --ignored                   # OCR test, needs tesseract with a language pack
cargo clippy --all-targets -- -D warnings
cargo fmt --check
scripts/bench.py --help                   # regenerate the benchmark table
scripts/demo.py --help                    # record the session at the top again

Built on lopdf (object model), hayro (text, rendering, positions and image decoding), rustybuzz (text shaping), subsetter (font embedding) and pulldown-cmark (Markdown).

Contributing

Bug reports are most useful with the file that shows the problem, or the smallest one that still does. Pull requests need cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test to pass, and a test for what they change: a fix comes with a test that fails without it.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    PDF extraction that actually works. The only extractor that audits every page. #2 on opendataloader-bench. 5 MCP tools for AI agents: metadata, convert, analyze, batch, structured extraction.
    7
    260 PyPI
    83
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Go MCP server for multi-format document access — PDF, TXT, MD, DOCX, CSV, images. 12 tools including OCR, search, table extraction, and URL fetch. Single binary, no runtime.
    13
    11
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Rust-powered PDF toolkit over MCP: create, read, and analyze PDFs; extract text and entities for RAG; convert to Markdown; split/merge/rotate/reorder pages; manage form fields and annotations; encrypt documents. Runs locally via uvx oxidize-mcp.
    12
    881 PyPI
    6
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A local MCP server that extracts text-layer content from PDF files, enabling AI agents to inspect, extract text, outlines, and page content.
    -