pdf-toolbox-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pdf-toolbox-mcp might be problematic. Click here for more details.
- pdf_toolbox_mcp-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- pdf_toolbox_mcp-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
- pdf_toolbox_mcp-0.1.0/.github/workflows/ci.yml +37 -0
- pdf_toolbox_mcp-0.1.0/.github/workflows/release.yml +20 -0
- pdf_toolbox_mcp-0.1.0/.gitignore +30 -0
- pdf_toolbox_mcp-0.1.0/CHANGELOG.md +27 -0
- pdf_toolbox_mcp-0.1.0/CONTRIBUTING.md +41 -0
- pdf_toolbox_mcp-0.1.0/LICENSE +21 -0
- pdf_toolbox_mcp-0.1.0/PKG-INFO +233 -0
- pdf_toolbox_mcp-0.1.0/PLAN.md +273 -0
- pdf_toolbox_mcp-0.1.0/README.md +202 -0
- pdf_toolbox_mcp-0.1.0/README.zh-CN.md +193 -0
- pdf_toolbox_mcp-0.1.0/RESEARCH.md +120 -0
- pdf_toolbox_mcp-0.1.0/SECURITY.md +21 -0
- pdf_toolbox_mcp-0.1.0/docs/competitor-matrix.md +126 -0
- pdf_toolbox_mcp-0.1.0/docs/cookbook.md +109 -0
- pdf_toolbox_mcp-0.1.0/docs/m1-record.md +84 -0
- pdf_toolbox_mcp-0.1.0/pyproject.toml +81 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/__init__.py +9 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/cli.py +345 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/__init__.py +40 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/assets.py +125 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/batch.py +87 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/compress.py +95 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/errors.py +47 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/forms.py +138 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/meta.py +129 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/ocr.py +120 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/pages.py +201 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/probe.py +178 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/render.py +61 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/sandbox.py +132 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/secure.py +313 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/engine/text.py +184 -0
- pdf_toolbox_mcp-0.1.0/src/pdf_toolbox/server.py +385 -0
- pdf_toolbox_mcp-0.1.0/tests/conftest.py +204 -0
- pdf_toolbox_mcp-0.1.0/tests/test_assets.py +37 -0
- pdf_toolbox_mcp-0.1.0/tests/test_engine.py +144 -0
- pdf_toolbox_mcp-0.1.0/tests/test_errors.py +106 -0
- pdf_toolbox_mcp-0.1.0/tests/test_inspect.py +34 -0
- pdf_toolbox_mcp-0.1.0/tests/test_locate.py +77 -0
- pdf_toolbox_mcp-0.1.0/tests/test_m3.py +88 -0
- pdf_toolbox_mcp-0.1.0/tests/test_m4.py +178 -0
- pdf_toolbox_mcp-0.1.0/tests/test_pages.py +71 -0
- pdf_toolbox_mcp-0.1.0/tests/test_probe.py +48 -0
- pdf_toolbox_mcp-0.1.0/tests/test_sandbox.py +69 -0
- pdf_toolbox_mcp-0.1.0/tests/test_secure.py +73 -0
- pdf_toolbox_mcp-0.1.0/tests/test_server.py +83 -0
- pdf_toolbox_mcp-0.1.0/tools/eval_ocr_zh.py +176 -0
- pdf_toolbox_mcp-0.1.0/tools/make_fixtures.py +85 -0
- pdf_toolbox_mcp-0.1.0/tools/mcp_probe.py +190 -0
- pdf_toolbox_mcp-0.1.0/uv.lock +2099 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something didn't work as expected
|
|
4
|
+
title: "[bug] "
|
|
5
|
+
labels: bug
|
|
6
|
+
body:
|
|
7
|
+
- type: textarea
|
|
8
|
+
id: what-happened
|
|
9
|
+
attributes:
|
|
10
|
+
label: What happened?
|
|
11
|
+
description: Also include what you expected instead.
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
- type: textarea
|
|
15
|
+
id: probe
|
|
16
|
+
attributes:
|
|
17
|
+
label: Dependency probe output
|
|
18
|
+
description: Output of `uv run pdftoolbox probe all` (or the `dependency_status` tool result)
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: error
|
|
23
|
+
attributes:
|
|
24
|
+
label: Structured error JSON
|
|
25
|
+
description: The `{"ok": false, "error": ...}` payload if any
|
|
26
|
+
- type: dropdown
|
|
27
|
+
id: os
|
|
28
|
+
attributes:
|
|
29
|
+
label: OS
|
|
30
|
+
options: [macOS, Linux, Windows]
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
- type: textarea
|
|
34
|
+
id: client
|
|
35
|
+
attributes:
|
|
36
|
+
label: MCP client / version
|
|
37
|
+
description: e.g. Claude Desktop 1.x, Claude Code 2.x, Cursor …
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest a new capability or improvement
|
|
4
|
+
title: "[feat] "
|
|
5
|
+
labels: enhancement
|
|
6
|
+
body:
|
|
7
|
+
- type: textarea
|
|
8
|
+
id: problem
|
|
9
|
+
attributes:
|
|
10
|
+
label: What do you want to do that isn't possible today?
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: proposal
|
|
15
|
+
attributes:
|
|
16
|
+
label: How would you expect to ask the agent to do it?
|
|
17
|
+
description: The natural-language phrasing matters — tools are designed around agent workflows.
|
|
18
|
+
- type: dropdown
|
|
19
|
+
id: fits
|
|
20
|
+
attributes:
|
|
21
|
+
label: Which area?
|
|
22
|
+
options:
|
|
23
|
+
- OCR / text extraction
|
|
24
|
+
- Page surgery (split/merge/rotate)
|
|
25
|
+
- Security (encrypt/redact/sanitize)
|
|
26
|
+
- Inspection (fonts/images/repair)
|
|
27
|
+
- Batch / performance
|
|
28
|
+
- Distribution / docs
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
include:
|
|
14
|
+
- os: ubuntu-latest
|
|
15
|
+
install: sudo apt-get update -qq && sudo apt-get install -y -qq poppler-utils qpdf ghostscript tesseract-ocr
|
|
16
|
+
- os: macos-latest
|
|
17
|
+
install: brew install poppler qpdf ghostscript tesseract
|
|
18
|
+
# Windows: choco 预装在 runner 上。tesseract 包较大(~5min),
|
|
19
|
+
# 换来全量 engine 测试在 Windows 真实运行——验证 gswin64c 别名等平台适配。
|
|
20
|
+
- os: windows-latest
|
|
21
|
+
install: choco install -y poppler qpdf ghostscript tesseract
|
|
22
|
+
runs-on: ${{ matrix.os }}
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: astral-sh/setup-uv@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
- name: Install system dependencies
|
|
29
|
+
run: ${{ matrix.install }}
|
|
30
|
+
- name: Lint (ruff)
|
|
31
|
+
run: uv run ruff check src tests tools
|
|
32
|
+
- name: Sync Python dependencies
|
|
33
|
+
run: uv sync --dev
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: uv run pytest -v
|
|
36
|
+
- name: Probe report
|
|
37
|
+
run: uv run pdftoolbox probe all
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # PyPI trusted publishing(OIDC,无需 API token)
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- name: Build
|
|
18
|
+
run: uv build
|
|
19
|
+
- name: Publish (trusted publishing)
|
|
20
|
+
run: uv publish
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.uv/
|
|
10
|
+
|
|
11
|
+
# 测试与工具
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
htmlcov/
|
|
17
|
+
|
|
18
|
+
# 编辑器与系统
|
|
19
|
+
.idea/
|
|
20
|
+
.vscode/
|
|
21
|
+
.DS_Store
|
|
22
|
+
|
|
23
|
+
# 项目运行产物(默认沙箱目录)
|
|
24
|
+
PDF-Toolbox/
|
|
25
|
+
.fixtures/
|
|
26
|
+
.tessdata/
|
|
27
|
+
|
|
28
|
+
# 本地私有材料(发版清单、教程草稿)与本机工具配置
|
|
29
|
+
notes/
|
|
30
|
+
opencode.json
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased] — 0.1.0
|
|
7
|
+
|
|
8
|
+
First functional build. Local-first PDF processing MCP server (OCRmyPDF + Poppler + qpdf + ghostscript), 24 tools, 105 tests green on macOS / Linux / Windows CI.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Content redaction**: `locate_text` (text → page + bounding boxes via `pdftotext -bbox`, CJK-safe normalized matching) and `redact_text` (redact by keyword — auto-locates every occurrence, no manual coordinates)
|
|
12
|
+
- **P1a core**: `pdf_info`, `is_searchable` (smart routing), `extract_text` (exact page ranges), `ocr_pdf` (**OCR write-back** to searchable PDF, language fallback), `render_pages` (PNG, optional inline image blocks), `unlock_pdf` (user-password decrypt — competitors hard-fail here)
|
|
13
|
+
- **P1b page surgery** (L0-pure qpdf): `split_pdf`, `merge_pdfs`, `rotate_pages`, `protect_pdf` (AES-256 + granular permissions)
|
|
14
|
+
- **P2 inspection & batch**: `list_fonts`, `extract_images`, `extract_attachments`, `check_repair` (+ rebuild repair), `linearize`, `batch_ocr` (per-file results / retries / timeouts)
|
|
15
|
+
- **P3 advanced**: `sanitize` (strip JS/metadata/attachments), `redact` (**true redaction** — affected pages rasterized, redacted text physically unrecoverable, other pages keep their text layer; `rasterize_all=true` for whole-doc protection), `fill_form`, `edit_metadata` (docinfo+XMP), `compress_pdf` (quality ladder to target size)
|
|
16
|
+
- Capability-leveled dependencies (L0–L3) with structured `missing_dependency` errors carrying per-platform install commands; `_deps` summary injected into every successful response
|
|
17
|
+
- CLI: `pdftoolbox` with all operations + `probe all`
|
|
18
|
+
- Windows: `gswin64c` binary alias, UTF-8 console output; CI runs the full suite on windows-latest via choco
|
|
19
|
+
- Research & plan docs (`RESEARCH.md`, `PLAN.md`, `docs/competitor-matrix.md` incl. hands-on survey of Citra / ODA / jztan / go-docs-mcp)
|
|
20
|
+
|
|
21
|
+
### Verified
|
|
22
|
+
- OCR write-back roundtrip (scan → searchable, marker asserted) on macOS + Linux + Windows
|
|
23
|
+
- Redaction security: output text layer empty + black-box pixel assertion
|
|
24
|
+
- Chinese OCR benchmark (synthetic): 99.03% clean / 93.69% deskewed / 81.07% heavy-noise
|
|
25
|
+
- 50-file batch: 50/50 success, ~1.6 s/file
|
|
26
|
+
|
|
27
|
+
[Unreleased]: https://github.com/twoer/pdf-toolbox-mcp/commits/main
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving pdf-toolbox-mcp!
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/twoer/pdf-toolbox-mcp
|
|
9
|
+
cd pdf-toolbox-mcp
|
|
10
|
+
uv sync --dev
|
|
11
|
+
uv run pdftoolbox probe all # 检查系统依赖级别(见 README 安装矩阵)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
System tools are capability-leveled — you can develop and run most tests with just one of them installed; engine tests auto-skip when a binary is missing.
|
|
15
|
+
|
|
16
|
+
## Before submitting
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv run ruff check src tests tools # lint 必须全绿
|
|
20
|
+
uv run pytest # 全量测试(按本机依赖自动跳过)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Both must pass. CI runs the full matrix (ubuntu / macos / windows) on every PR.
|
|
24
|
+
|
|
25
|
+
## Adding a tool
|
|
26
|
+
|
|
27
|
+
1. Implement the engine function in `src/pdf_toolbox/engine/` — pure function, `dict` in / `dict` out, no MCP imports; call `require()` for binaries and `assert_readable()`/`check_write()` for path safety.
|
|
28
|
+
2. Wrap it in `server.py` via `_guard()` (structured errors + `_deps` injection) and add a `cli.py` command.
|
|
29
|
+
3. Tests in `tests/` with the right capability marker (`requires_poppler` / `requires_qpdf` / `requires_tesseract` / `requires_gs`).
|
|
30
|
+
4. Update both READMEs' tool table and `CHANGELOG.md`.
|
|
31
|
+
|
|
32
|
+
## Conventions
|
|
33
|
+
|
|
34
|
+
- Errors raise typed exceptions from `engine/errors.py`; the MCP layer converts them to `{"ok": false, "error": <code>, ...}`.
|
|
35
|
+
- Outputs never overwrite silently — `overwrite=true` must be explicit.
|
|
36
|
+
- All subprocess calls use argument lists (no shell interpolation).
|
|
37
|
+
- Docs and user-facing messages are bilingual (English README + `README.zh-CN.md`); code comments in Chinese are fine.
|
|
38
|
+
|
|
39
|
+
## Reporting issues
|
|
40
|
+
|
|
41
|
+
Please include `uv run pdftoolbox probe all` output and the structured error JSON you got.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zhangkun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pdf-toolbox-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first PDF processing MCP server — OCR write-back, text extraction, page rendering, page surgery, encryption (OCRmyPDF + Poppler + qpdf)
|
|
5
|
+
Project-URL: Homepage, https://github.com/twoer/pdf-toolbox-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/twoer/pdf-toolbox-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/twoer/pdf-toolbox-mcp/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/twoer/pdf-toolbox-mcp/commits/main/
|
|
9
|
+
Author: zhangkun
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: local-first,mcp,model-context-protocol,ocr,ocrmypdf,pdf,poppler,qpdf
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Text Processing
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: fastmcp>=2.0
|
|
26
|
+
Requires-Dist: ocrmypdf>=16.0
|
|
27
|
+
Requires-Dist: pikepdf>=9.0
|
|
28
|
+
Requires-Dist: pillow>=11.0
|
|
29
|
+
Requires-Dist: typer>=0.12
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# pdf-toolbox-mcp
|
|
33
|
+
|
|
34
|
+
[中文文档](README.zh-CN.md) | Local-first PDF processing for AI agents.
|
|
35
|
+
|
|
36
|
+
**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.
|
|
37
|
+
|
|
38
|
+
## Why another PDF MCP?
|
|
39
|
+
|
|
40
|
+
The PDF MCP space is crowded — but only on the *reading* side. Based on a [hands-on survey of the ecosystem](docs/competitor-matrix.md) (2026-09):
|
|
41
|
+
|
|
42
|
+
| Capability | **pdf-toolbox** | Citra (916★) | ODA PDF-Tools (153★) | jztan/pdf-mcp (130★) | Cloud SaaS MCPs |
|
|
43
|
+
|---|:-:|:-:|:-:|:-:|:-:|
|
|
44
|
+
| **OCR write-back** → searchable PDF file | ✅ | ❌ read-out only | ❌ (no OCR) | ❌ read-out only | ☁️ paid |
|
|
45
|
+
| **Unlock encrypted** (user password) | ✅ | ❌ hard fail | ⚠️ owner-pw only | ❌ hard fail | ☁️ paid |
|
|
46
|
+
| Split / merge / rotate | ✅ | ❌ | ✅ | ❌ | ☁️ paid |
|
|
47
|
+
| **Compress** to target size | ✅ | ❌ | ❌ | ❌ | ☁️ paid |
|
|
48
|
+
| Render pages for vision | ✅ | ✅ | ✅ | ✅ | ☁️ |
|
|
49
|
+
| 100% local & private | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
50
|
+
|
|
51
|
+
Pain points this addresses directly:
|
|
52
|
+
|
|
53
|
+
- 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_pdf` decrypts with just the user password.
|
|
54
|
+
- Claude Code burns **~30× more tokens** reading a PDF page-as-image than extracting text locally.
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
Add to any MCP client (Claude Desktop / Claude Code / Cursor / …):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"pdf-toolbox": {
|
|
64
|
+
"command": "uvx",
|
|
65
|
+
"args": ["--from", "git+https://github.com/twoer/pdf-toolbox-mcp", "pdf-toolbox-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
*(PyPI package `pdf-toolbox-mcp` is coming; the git install above works today.)*
|
|
72
|
+
|
|
73
|
+
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:
|
|
74
|
+
|
|
75
|
+
| Level | Binary | Unlocks | macOS | Debian/Ubuntu | Windows |
|
|
76
|
+
|---|---|---|---|---|---|
|
|
77
|
+
| L0 | qpdf | split / merge / rotate / protect / unlock | `brew install qpdf` | `apt install qpdf` | `choco/scoop install qpdf` |
|
|
78
|
+
| L1 | poppler | extract_text / render / info | `brew install poppler` | `apt install poppler-utils` | `choco/scoop install poppler` or conda-forge |
|
|
79
|
+
| L2 | tesseract | **ocr_pdf (write-back)** | `brew install tesseract tesseract-lang` | `apt install tesseract-ocr tesseract-ocr-chi-sim` | `choco/scoop install tesseract` |
|
|
80
|
+
| L3 | ghostscript | compress | `brew install ghostscript` | `apt install ghostscript` | `scoop install ghostscript` / `winget install ArtifexSoftware.GhostScript` |
|
|
81
|
+
|
|
82
|
+
> Windows note: Ghostscript's binary is `gswin64c.exe` there — the probe detects it automatically, so `compress_pdf` works out of the box. Tesseract language packs (e.g. `chi_sim`) must be downloaded to its `tessdata` folder separately.
|
|
83
|
+
|
|
84
|
+
Every successful response carries a `_deps` summary (`{"level": 2, "missing": ["gs"]}`) so the agent always knows what's available.
|
|
85
|
+
|
|
86
|
+
## Tools (24)
|
|
87
|
+
|
|
88
|
+
| Tool | What it does | Engine |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `pdf_info` | Pages, encryption status, metadata — always call first | pdfinfo |
|
|
91
|
+
| `is_searchable` | Smart routing: text density check → recommends `extract_text` or `ocr_pdf` | pdftotext |
|
|
92
|
+
| `extract_text` | Layout-aware text, exact page ranges `1-3,5`, per-page mode | pdftotext |
|
|
93
|
+
| `ocr_pdf` | **OCR write-back**: scan → searchable PDF (deskew, skip/redo, lang fallback) | OCRmyPDF |
|
|
94
|
+
| `batch_ocr` | Whole-directory OCR with per-file results, retries, timeouts | OCRmyPDF |
|
|
95
|
+
| `render_pages` | PNG per page, `return_images=true` streams image blocks to the vision model | pdftoppm |
|
|
96
|
+
| `extract_images` | Pull embedded images (inventory or PNG files) | pdfimages |
|
|
97
|
+
| `extract_attachments` | Pull embedded attachment files | pdfdetach |
|
|
98
|
+
| `list_fonts` | Font audit — non-embedded fonts risk missing glyphs on other machines | pdffonts |
|
|
99
|
+
| `unlock_pdf` | Decrypt with **user password**, output a clean decrypted file | qpdf |
|
|
100
|
+
| `protect_pdf` | AES-256 + granular permissions (print/extract/modify/…) | qpdf |
|
|
101
|
+
| `split_pdf` | By ranges or every N pages | qpdf |
|
|
102
|
+
| `merge_pdfs` | Ordered merge | qpdf |
|
|
103
|
+
| `rotate_pages` | 90/180/270 on selected pages | qpdf |
|
|
104
|
+
| `check_repair` | Structural check; `repair=true` rebuilds damaged files | qpdf |
|
|
105
|
+
| `linearize` | Web-optimized progressive-loading output | qpdf |
|
|
106
|
+
| `sanitize` | Publishing hygiene: strip JS/OpenAction/metadata/attachments | pikepdf |
|
|
107
|
+
| `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 |
|
|
108
|
+
| `redact_text` | Redact **by content**: locate every occurrence of the given keywords and black them out — no manual coordinates needed | pdftotext -bbox |
|
|
109
|
+
| `locate_text` | Find where text occurs: page + bounding boxes (PDF points, top-left origin) — the foundation for redaction & highlighting | pdftotext -bbox |
|
|
110
|
+
| `fill_form` | Fill AcroForm fields (missing fields reported) | pikepdf |
|
|
111
|
+
| `edit_metadata` | Set/clear Title/Author/… (docinfo + XMP) | pikepdf |
|
|
112
|
+
| `compress_pdf` | Compress, optionally down a quality ladder until hitting `target_mb` | ghostscript |
|
|
113
|
+
| `dependency_status` | Probe system tools + install commands | — |
|
|
114
|
+
|
|
115
|
+
**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`, …
|
|
116
|
+
|
|
117
|
+
## Examples
|
|
118
|
+
|
|
119
|
+
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:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
PTX="uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox"
|
|
123
|
+
# shortens to "uvx --from pdf-toolbox-mcp pdftoolbox" once the PyPI package lands
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**1 · Scan → searchable PDF** (the flagship)
|
|
127
|
+
|
|
128
|
+
> “`contract-scan.pdf` is a scanned contract I can't search. Make it searchable — mostly Chinese with some English.”
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
$PTX ocr contract-scan.pdf --lang chi_sim+eng
|
|
134
|
+
$PTX text contract-scan_ocr.pdf --pages 1-3
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**2 · Encrypted PDF → readable**
|
|
138
|
+
|
|
139
|
+
> “`locked.pdf` is password-protected; the password is `hunter2`. Unlock it and summarize page 3.”
|
|
140
|
+
|
|
141
|
+
Agent: `unlock_pdf(path, password="hunter2")` → `locked_unlocked.pdf` → `extract_text(pages="3")`.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
$PTX unlock locked.pdf --password 'hunter2'
|
|
145
|
+
$PTX text locked_unlocked.pdf --pages 3
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**3 · Redact secrets before sharing**
|
|
149
|
+
|
|
150
|
+
> “Black out every occurrence of `张三` and `HT-2026-088` in `draft.pdf` — it must be physically unrecoverable.”
|
|
151
|
+
|
|
152
|
+
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.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
$PTX redact-text draft.pdf --query 张三 --query HT-2026-088
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**4 · Assemble & encrypt for sending**
|
|
159
|
+
|
|
160
|
+
> “Merge `cover.pdf` + `report.pdf` into `annual.pdf`, then protect it: opens with password `k3y`, printing allowed, modification not.”
|
|
161
|
+
|
|
162
|
+
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`.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
$PTX merge cover.pdf report.pdf --output annual.pdf
|
|
166
|
+
$PTX protect annual.pdf --user-password 'k3y'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**5 · Fit an email size limit**
|
|
170
|
+
|
|
171
|
+
> “`big.pdf` is 38 MB and the mail cap is 10 MB. Shrink it.”
|
|
172
|
+
|
|
173
|
+
Agent: `compress_pdf(path, target_mb=10)` walks the quality ladder (ebook → screen) until under target → `big_compressed.pdf`.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
$PTX compress big.pdf --target-mb 10
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
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](docs/cookbook.md).
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
| Env | Default | Meaning |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `PDF_TOOLBOX_TESS_LANG` | `chi_sim+eng` | Default OCR languages; missing packs auto-fallback (flagged via `lang_fallback`) |
|
|
186
|
+
| `PDF_TOOLBOX_WORKSPACE` | unset | If set, all writes are confined to this directory; system dirs are always denied |
|
|
187
|
+
|
|
188
|
+
## CLI
|
|
189
|
+
|
|
190
|
+
Everything is also available headless (great for scripts and CI):
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox ocr scan.pdf --lang chi_sim+eng
|
|
194
|
+
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox unlock locked.pdf --password 'xxx'
|
|
195
|
+
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox split big.pdf --every-n 10
|
|
196
|
+
uvx --from git+https://github.com/twoer/pdf-toolbox-mcp pdftoolbox probe all
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
*(Shortens to `uvx --from pdf-toolbox-mcp …` once the PyPI package is published — see the note in Quick start.)*
|
|
200
|
+
|
|
201
|
+
## Security & privacy
|
|
202
|
+
|
|
203
|
+
- No network calls. Files never leave the machine.
|
|
204
|
+
- All subprocess calls use argument lists (no shell interpolation); page-range parsing is shared and validated.
|
|
205
|
+
- Outputs never silently overwrite: `overwrite=true` must be passed explicitly.
|
|
206
|
+
- Passwords are never logged in error payloads.
|
|
207
|
+
- Untrusted PDF content is flagged in tool descriptions (prompt-injection awareness).
|
|
208
|
+
|
|
209
|
+
## License compliance
|
|
210
|
+
|
|
211
|
+
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](PLAN.md) §7 for the full table.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv sync --dev # install
|
|
217
|
+
uv run pytest # 105 tests; auto-skip by capability level
|
|
218
|
+
uv run pdftoolbox probe all
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Cross-platform check without leaving macOS:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
docker run --rm -v "$PWD":/src:ro python:3.12-slim bash -c \
|
|
225
|
+
'apt-get update -qq >/dev/null && apt-get install -y -qq poppler-utils tesseract-ocr qpdf ghostscript >/dev/null &&
|
|
226
|
+
pip install -q uv && cp -r /src /work && cd /work && uv sync --dev --quiet && uv run pytest -q'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
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](PLAN.md).
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|