pdf-toolbox-mcp
中文文档 | Local-first PDF processing for AI agents.
Others help AI read PDFs. This one helps AI process them — OCR a scan into a truly searchable file, unlock encrypted PDFs, split/merge/rotate, re-encrypt for sharing. 100% on your machine: no cloud calls, no file uploads, no per-page fees.
Why another PDF MCP?
The PDF MCP space is crowded — but only on the reading side. Based on a hands-on survey of the ecosystem (2026-09):
| Capability | pdf-toolbox | Citra (916★) | ODA PDF-Tools (153★) | jztan/pdf-mcp (130★) | Cloud SaaS MCPs |
|---|---|---|---|---|---|
| OCR write-back → searchable PDF file | ✅ | ❌ read-out only | ❌ (no OCR) | ❌ read-out only | ☁️ paid |
| Unlock encrypted (user password) | ✅ | ❌ hard fail | ⚠️ owner-pw only | ❌ hard fail | ☁️ paid |
| Split / merge / rotate | ✅ | ❌ | ✅ | ❌ | ☁️ paid |
| Compress to target size | ✅ | ❌ | ❌ | ❌ | ☁️ paid |
| Render pages for vision | ✅ | ✅ | ✅ | ✅ | ☁️ |
| 100% local & private | ✅ | ✅ | ✅ | ✅ | ❌ |
Pain points this addresses directly:
- Claude natively refuses encrypted PDFs; ChatGPT reports "No text could be extracted" on scans — here, OCR writes a real text layer back into the file, and
unlock_pdfdecrypts with just the user password. - Claude Code burns ~30× more tokens reading a PDF page-as-image than extracting text locally.
Quick start
Add to any MCP client (Claude Desktop / Claude Code / Cursor / …):
{
"mcpServers": {
"pdf-toolbox": {
"command": "uvx",
"args": ["--from", "git+https://github.com/twoer/pdf-toolbox-mcp", "pdf-toolbox-mcp"]
}
}
}
(PyPI package pdf-toolbox-mcp is coming; the git install above works today.)
Python dependencies resolve automatically. System tools are capability-leveled — missing ones never crash the server; the tool returns a structured error with the exact install command:
| Level | Binary | Unlocks | macOS | Debian/Ubuntu | Windows |
|---|---|---|---|---|---|
| L0 | qpdf | split / merge / rotate / protect / unlock | brew install qpdf |
apt install qpdf |
choco/scoop install qpdf |
| L1 | poppler | extract_text / render / info | brew install poppler |
apt install poppler-utils |
choco/scoop install poppler or conda-forge |
| L2 | tesseract | ocr_pdf (write-back) | brew install tesseract tesseract-lang |
apt install tesseract-ocr tesseract-ocr-chi-sim |
choco/scoop install tesseract |
| L3 | ghostscript | compress | brew install ghostscript |
apt install ghostscript |
scoop install ghostscript / winget install ArtifexSoftware.GhostScript |
Windows note: Ghostscript's binary is
gswin64c.exethere — the probe detects it automatically, socompress_pdfworks out of the box. Tesseract language packs (e.g.chi_sim) must be downloaded to itstessdatafolder separately.
Every successful response carries a _deps summary ({"level": 2, "missing": ["gs"]}) so the agent always knows what's available.
Tools (24)
| Tool | What it does | Engine |
|---|---|---|
pdf_info |
Pages, encryption status, metadata — always call first | pdfinfo |
is_searchable |
Smart routing: text density check → recommends extract_text or ocr_pdf |
pdftotext |
extract_text |
Layout-aware text, exact page ranges 1-3,5, per-page mode |
pdftotext |
ocr_pdf |
OCR write-back: scan → searchable PDF (deskew, skip/redo, lang fallback) | OCRmyPDF |
batch_ocr |
Whole-directory OCR with per-file results, retries, timeouts | OCRmyPDF |
render_pages |
PNG per page, return_images=true streams image blocks to the vision model |
pdftoppm |
extract_images |
Pull embedded images (inventory or PNG files) | pdfimages |
extract_attachments |
Pull embedded attachment files | pdfdetach |
list_fonts |
Font audit — non-embedded fonts risk missing glyphs on other machines | pdffonts |
unlock_pdf |
Decrypt with user password, output a clean decrypted file | qpdf |
protect_pdf |
AES-256 + granular permissions (print/extract/modify/…) | qpdf |
split_pdf |
By ranges or every N pages | qpdf |
merge_pdfs |
Ordered merge | qpdf |
rotate_pages |
90/180/270 on selected pages | qpdf |
check_repair |
Structural check; repair=true rebuilds damaged files |
qpdf |
linearize |
Web-optimized progressive-loading output | qpdf |
sanitize |
Publishing hygiene: strip JS/OpenAction/metadata/attachments | pikepdf |
redact |
True redaction: affected pages rasterized + opaque boxes — redacted text physically unrecoverable, other pages keep their text layer (rasterize_all=true for max protection) |
pdftoppm + PIL |
redact_text |
Redact by content: locate every occurrence of the given keywords and black them out — no manual coordinates needed | pdftotext -bbox |
locate_text |
Find where text occurs: page + bounding boxes (PDF points, top-left origin) — the foundation for redaction & highlighting | pdftotext -bbox |
fill_form |
Fill AcroForm fields (missing fields reported) | pikepdf |
edit_metadata |
Set/clear Title/Author/… (docinfo + XMP) | pikepdf |
compress_pdf |
Compress, optionally down a quality ladder until hitting target_mb |
ghostscript |
dependency_status |
Probe system tools + install commands | — |
Error contract (agents self-route): failures return {"ok": false, "error": "<code>"} — missing_dependency (with install per platform), encrypted_pdf (hint: call unlock_pdf first), wrong_password, output_exists (explicit overwrite required), invalid_page_range, …
Examples
In an MCP client, just describe the outcome — the agent chains the tools itself, and the error contract makes it self-routing (an encrypted_pdf error tells it to call unlock_pdf first, and so on). For headless use, define once:
PTX="uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox"
# shortens to "uvx --from pdf-toolbox-mcp pdftoolbox" once the PyPI package lands
1 · Scan → searchable PDF (the flagship)
“
contract-scan.pdfis a scanned contract I can't search. Make it searchable — mostly Chinese with some English.”
Agent: pdf_info → is_searchable reports low text density → ocr_pdf(path, lang="chi_sim+eng") writes contract-scan_ocr.pdf. Text extraction and Ctrl+F now work on the output.
$PTX ocr contract-scan.pdf --lang chi_sim+eng
$PTX text contract-scan_ocr.pdf --pages 1-3
2 · Encrypted PDF → readable
“
locked.pdfis password-protected; the password ishunter2. Unlock it and summarize page 3.”
Agent: unlock_pdf(path, password="hunter2") → locked_unlocked.pdf → extract_text(pages="3").
$PTX unlock locked.pdf --password 'hunter2'
$PTX text locked_unlocked.pdf --pages 3
3 · Redact secrets before sharing
“Black out every occurrence of
张三andHT-2026-088indraft.pdf— it must be physically unrecoverable.”
Agent: redact_text(queries=["张三", "HT-2026-088"]) → draft_redacted.pdf. Pages containing hits are rasterized, so the strings vanish from the pixels and the text layer; other pages keep their selectable text. Verify by running extract_text on the output: zero hits expected.
$PTX redact-text draft.pdf --query 张三 --query HT-2026-088
4 · Assemble & encrypt for sending
“Merge
cover.pdf+report.pdfintoannual.pdf, then protect it: opens with passwordk3y, printing allowed, modification not.”
Agent: merge_pdfs(paths=["cover.pdf", "report.pdf"], output="annual.pdf") → protect_pdf(user_password="k3y") (print/extract allowed, modify denied by default) → annual_locked.pdf.
$PTX merge cover.pdf report.pdf --output annual.pdf
$PTX protect annual.pdf --user-password 'k3y'
5 · Fit an email size limit
“
big.pdfis 38 MB and the mail cap is 10 MB. Shrink it.”
Agent: compress_pdf(path, target_mb=10) walks the quality ladder (ebook → screen) until under target → big_compressed.pdf.
$PTX compress big.pdf --target-mb 10
More recipes — batch OCR, the publish-hygiene chain (sanitize → edit_metadata → linearize), vision rendering, locate-and-redact, form filling, damaged-file rescue — in the cookbook.
Configuration
| Env | Default | Meaning |
|---|---|---|
PDF_TOOLBOX_TESS_LANG |
chi_sim+eng |
Default OCR languages; missing packs auto-fallback (flagged via lang_fallback) |
PDF_TOOLBOX_WORKSPACE |
unset | If set, all writes are confined to this directory; system dirs are always denied |
CLI
Everything is also available headless (great for scripts and CI):
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox ocr scan.pdf --lang chi_sim+eng
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox unlock locked.pdf --password 'xxx'
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox split big.pdf --every-n 10
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox probe all
(Shortens to uvx --from pdf-toolbox-mcp … once the PyPI package is published — see the note in Quick start.)
Security & privacy
- No network calls. Files never leave the machine.
- All subprocess calls use argument lists (no shell interpolation); page-range parsing is shared and validated.
- Outputs never silently overwrite:
overwrite=truemust be passed explicitly. - Passwords are never logged in error payloads.
- Untrusted PDF content is flagged in tool descriptions (prompt-injection awareness).
License compliance
MIT. System tools are invoked as independent processes (aggregation): poppler (GPL-2.0), qpdf (Apache-2.0), tesseract (Apache-2.0), ghostscript (AGPL, optional); Python deps ocrmypdf/pikepdf are MPL-2.0. See PLAN.md §7 for the full table.
Development
uv sync --dev # install
uv run pytest # 105 tests; auto-skip by capability level
uv run pdftoolbox probe all
Cross-platform check without leaving macOS:
docker run --rm -v "$PWD":/src:ro python:3.12-slim bash -c \
'apt-get update -qq >/dev/null && apt-get install -y -qq poppler-utils tesseract-ocr qpdf ghostscript >/dev/null &&
pip install -q uv && cp -r /src /work && cd /work && uv sync --dev --quiet && uv run pytest -q'
Roadmap: v0.1.0 ships all 24 tools above. Next up: the PyPI package (drops the git prefix from every command) and hardening against real-world scanned documents. Explicit non-goals: editing existing text, password cracking — see PLAN.md.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pdf_toolbox_mcp-0.1.1.tar.gz.
File metadata
- Download URL: pdf_toolbox_mcp-0.1.1.tar.gz
- Upload date:
- Size: 200.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8abed7d060042efa566fe7651d2f25ce8d0ac4822fabfd89c82c980f8790244e
|
|
| MD5 |
e34e81b461a2f19b1ccbaf4ad69d7c39
|
|
| BLAKE2b-256 |
9af44c954338d69161b8f7260bc5af275953193a859fe0cb0779c907ac3be672
|
File details
Details for the file pdf_toolbox_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pdf_toolbox_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 43.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f389c1e07c8acbeeee34c12cd5018d1f7f360d1c033f1339d3de85341e1e04d0
|
|
| MD5 |
f46a11ef9d000ccfc63fc02ab53606eb
|
|
| BLAKE2b-256 |
3e9f4d419396a94f5fd7e1c9bfc62714003e2a99a3b36976dcf5b09b603f29f2
|