oxidize-pdf
<!-- mcp-name: io.github.bzsanti/oxidize-pdf-mcp -->
# oxidize-pdf
> Rust core pinned to **5.4.2**. See [compatibility and validation](docs/UPSTREAM-5.4.2.md).
<!-- mcp-name: io.github.bzsanti/oxidize-pdf-mcp -->
[](https://pypi.org/project/oxidize-pdf/)
[](https://github.com/bzsanti/oxidize-python/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://pypi.org/project/oxidize-pdf/)
[](https://github.com/bzsanti/oxidize-python)
[](https://modelcontextprotocol.io/)
[](https://glama.ai/mcp/servers/bzsanti/oxidize-python)
**Rust-powered PDF library for Python.** Generate, parse, split, merge, and manipulate PDFs with native performance. Ships with a built-in [MCP server](#mcp-server) so AI agents can work with PDFs out of the box.
No C dependencies. No Java. No subprocess calls.
## Installation
```bash
pip install oxidize-pdf # Core library
pip install "oxidize-pdf[mcp]" # + MCP server for AI agents
```
**Platforms:** Linux (x86_64, aarch64) | macOS (x86_64, Apple Silicon) | Windows (x86_64)
**Requires:** Python 3.10+
Version 0.22.0 pins Rust core **5.4.2**, using the MIT-licensed `oxidize-webpki`
RustCrypto provider for certificate verification. See the
[dependency policy](docs/NO-NATIVE-CRYPTO.md) for supported builds and limits.
New APIs include explicit text recovery, non-breaking-space normalization,
build identification controls and incremental signature slots. See the
[API guide and validation](docs/UPSTREAM-5.4.1.md).
## Why oxidize-pdf?
| | oxidize-pdf | Pure-Python libs | C/Java wrappers |
|---|---|---|---|
| **Performance** | Native (compiled Rust) | Interpreted | Native but heavy |
| **Dependencies** | Zero | Varies | Poppler, Java, Ghostscript |
| **Memory safety** | Rust ownership model | GC-dependent | Manual / GC |
| **Type stubs** | Full (mypy/pyright) | Partial | Rare |
| **AI-ready (MCP)** | Built-in | No | No |
---
## MCP Server
Give your AI agent full PDF capabilities in one line:
```bash
oxidize-mcp
```
The built-in [Model Context Protocol](https://modelcontextprotocol.io/) server exposes **12 tools**, **5 static resources plus a session resource template**, and **5 prompts** over stdio. Install `oxidize-pdf[mcp]` to include its dependencies; the base library keeps MCP optional.
Version 0.20 uses FastMCP 4 and MCP Python SDK 2, with support for both modern and legacy stdio clients. See the [migration guide](docs/MCP-V2-MIGRATION.md) for versions and installation routes.
If `tools/list` returns an empty array with an older installation (issue [#198](https://github.com/bzsanti/oxidize-python/issues/198)), upgrade to 0.20.0 or newer. These versions fix tool registration when starting `server.py` directly. On Windows, use the same Python environment for installation and startup:
```powershell
python -m pip install --upgrade "oxidize-pdf[mcp]"
python -m oxidize_pdf.mcp.server
```
For a custom JSON-RPC client, send `initialize`, wait for its response, then send `notifications/initialized` before `tools/list`. Read newline-delimited JSON-RPC from stdout and keep stderr separate; the startup banner is written to stderr.
### Claude Desktop integration
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"oxidize-pdf": {
"command": "oxidize-mcp",
"env": {
"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"
}
}
}
}
```
### GitHub Copilot (VS Code) integration
Copilot's agent mode speaks MCP. Add `.vscode/mcp.json` to your workspace:
```json
{
"servers": {
"oxidize-pdf": {
"command": "oxidize-mcp",
"env": {
"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"
}
}
}
}
```
Open the Chat view, switch to **Agent** mode, and the 12 PDF tools appear in
the tool picker. (The same block also works under the `mcp.servers` key in your
user `settings.json` if you prefer a global install.)
### OpenAI Agents SDK integration
The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/mcp/)
spawns the server over stdio and exposes its tools to an agent:
```python
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async with MCPServerStdio(
params={"command": "oxidize-mcp", "env": {"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"}},
cache_tools_list=True,
) as server:
agent = Agent(
name="PDF assistant",
instructions="Use the oxidize-pdf tools to inspect and manipulate PDFs.",
mcp_servers=[server],
)
result = await Runner.run(agent, "How many pages does report.pdf have?")
print(result.final_output)
```
A runnable version is in [`examples/openai_agents_quickstart.py`](examples/openai_agents_quickstart.py).
> Both integrations run the server **locally over stdio**, so its tools operate
> on PDFs in the configured workspace directory. This package does not expose
> a hosted HTTP endpoint. ChatGPT web needs a remote connection; developer-mode
> testing can also use [Secure MCP Tunnel](https://developers.openai.com/plugins/deploy/connect-chatgpt)
> with a local stdio server. No ChatGPT plugin is published by this migration.
### Available tools
| Tool | What it does |
|------|-------------|
| `read_pdf` | Read metadata — page count, version, encryption status, title, author |
| `extract_text` | Extract text from all pages or a specific page |
| `convert_pdf` | Convert to markdown, chunks, or RAG-optimized format |
| `create_pdf` | Create a new PDF with optional metadata |
| `save_pdf` | Save a session to disk, with optional encryption |
| `add_content` | Add pages, text, and graphics to a session |
| `annotate_pdf` | Add text annotations and highlights |
| `manipulate_pdf` | Split, merge, rotate, extract pages, reverse, overlay |
| `manage_forms` | Create, fill, read, and validate form fields |
| `secure_pdf` | Encrypt, check permissions, verify signatures |
| `extract_entities` | Extract structured entities from pages |
| `analyze_pdf` | Validate structure, detect corruption, check PDF/A compliance |
The server also exposes **resources** (session data, capabilities, version info) and **prompts** (guided workflows for summarization, data extraction, form filling, and more).
### Configuration
```bash
OXIDIZE_WORKSPACE=/path/to/pdfs oxidize-mcp
```
The server is configured entirely through environment variables:
| Variable | Default | Purpose |
| --- | --- | --- |
| `OXIDIZE_WORKSPACE` | `~/Documents/oxidize-mcp` | Sandbox root; all paths must resolve inside it. |
| `OXIDIZE_ALLOWED_PATHS` | _(none)_ | Comma-separated extra directories allowed outside the workspace. |
| `OXIDIZE_MAX_FILE_SIZE_MB` | `100` | Reject input PDFs larger than this on disk. |
| `OXIDIZE_MAX_PAGES` | `10000` | Reject documents with more pages than this before any extraction work. |
| `OXIDIZE_MAX_OUTPUT_BYTES` | `10485760` | Cap the serialized size of a tool's JSON response (10 MB). |
| `OXIDIZE_MAX_SESSIONS` | `10` | Maximum concurrent stateful PDF-creation sessions. |
| `OXIDIZE_MAX_SESSION_BYTES` | `10485760` | Cap the content a single session may accumulate (10 MB). |
| `OXIDIZE_SESSION_TIMEOUT` | `3600` | Session expiry, in seconds. |
Resource caps (`OXIDIZE_MAX_*`) protect the server from a large or malicious
PDF: oversized documents are rejected up front and tool responses are bounded
rather than serialized unbounded. Exceeding a cap returns an error with code
`RESOURCE_LIMIT`.
Or start programmatically:
```python
from oxidize_pdf.mcp.server import run
run()
```
---
## Python API
### Create a PDF
```python
from oxidize_pdf import Document, Page, Font, Color
doc = Document()
doc.set_title("My Document")
doc.set_author("Jane Doe")
page = Page.a4()
page.set_font(Font.HELVETICA, 24.0)
page.set_text_color(Color.black())
page.text_at(72.0, 750.0, "Hello from oxidize-pdf!")
page.set_font(Font.TIMES_ROMAN, 12.0)
page.text_at(72.0, 700.0, "Generated with Python + Rust.")
doc.add_page(page)
doc.save("output.pdf")
```
### Parse an existing PDF
```python
from oxidize_pdf import PdfReader
reader = PdfReader.open("document.pdf")
print(f"Pages: {reader.page_count}, Version: {reader.version}")
for i, text in enumerate(reader.extract_text()):
print(f"--- Page {i + 1} ---")
print(text)
```
### Operations
```python
from oxidize_pdf import split_pdf, merge_pdfs, rotate_pdf, extract_pages
split_pdf("input.pdf", "output_dir/") # Split into individual pages
merge_pdfs(["part1.pdf", "part2.pdf"], "merged.pdf") # Merge multiple PDFs
rotate_pdf("input.pdf", "rotated.pdf", 90) # Rotate all pages
extract_pages("input.pdf", "subset.pdf", [0, 2, 4]) # Extract specific pages
```
### Graphics
```python
from oxidize_pdf import Document, Page, Color
doc = Document()
page = Page.a4()
page.set_fill_color(Color.hex("#3498db"))
page.draw_rect(72.0, 700.0, 200.0, 100.0)
page.fill()
page.set_stroke_color(Color.red())
page.set_line_width(2.0)
page.draw_circle(300.0, 500.0, 50.0)
page.stroke()
doc.add_page(page)
doc.save("graphics.pdf")
```
### Types
```python
from oxidize_pdf import Color, Point, Rectangle, Margins, Font
# Colors
Color.rgb(1.0, 0.0, 0.0) # RGB
Color.hex("#ff6600") # Hex
Color.cmyk(0.0, 1.0, 1.0, 0.0) # CMYK
# Geometry
Point(72.0, 720.0)
Rectangle.from_xywh(72.0, 72.0, 468.0, 648.0)
Margins.uniform(72.0)
# Fonts — all 14 standard PDF fonts
Font.HELVETICA # Font.HELVETICA_BOLD
Font.TIMES_ROMAN # Font.TIMES_BOLD
Font.COURIER # Font.COURIER_BOLD
```
### Error handling
```python
from oxidize_pdf import PdfReader, PdfError, PdfIoError, PdfParseError
try:
reader = PdfReader.open("missing.pdf")
except PdfIoError as e:
print(f"I/O error: {e}")
except PdfParseError as e:
print(f"Parse error: {e}")
except PdfError as e:
print(f"PDF error: {e}")
```
Exception hierarchy: `PdfError` > `PdfIoError`, `PdfParseError`, `PdfEncryptionError`, `PdfPermissionError`
## MCP Server
oxidize-pdf includes an [MCP](https://modelcontextprotocol.io/) server that exposes PDF capabilities to AI assistants like Claude. Install with the `mcp` extra:
```bash
pip install oxidize-pdf[mcp]
```
### Claude Desktop
Add this to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"oxidize-pdf": {
"command": "uvx",
"args": ["--from", "oxidize-pdf[mcp]", "oxidize-mcp"]
}
}
}
```
### Claude Code
```bash
claude mcp add oxidize-pdf -- uvx --from "oxidize-pdf[mcp]" oxidize-mcp
```
### Available tools
| Tool | Description |
|---|---|
| `read_pdf` | Open a PDF and get metadata (pages, version, encryption) |
| `extract_text` | Extract text content from PDF pages |
| `convert_pdf` | Convert between PDF versions |
| `analyze_pdf` | Analyze structure, fonts, images, and compliance |
| `extract_entities` | Extract images and digital signatures |
| `manipulate_pdf` | Split, merge, rotate, extract, and reorder pages |
| `annotate_pdf` | Add text annotations, highlights, and stamps |
| `manage_forms` | Create, fill, and read PDF form fields |
| `secure_pdf` | Encrypt, decrypt, and set document permissions |
| `create_pdf` | Create a new PDF document with pages |
| `add_pdf_content` | Add text, shapes, and images to pages |
| `save_pdf` | Save the document to file or bytes |
### Resources
- `oxidize://fonts` — Available built-in PDF fonts
- `oxidize://page-sizes` — Standard page sizes with dimensions
- `oxidize://capabilities` — Server capabilities and tool listing
- `oxidize://version` — Version information
- `oxidize://workspace` — PDF files in the workspace directory
- `oxidize://session/{id}` — Session data by ID
## Known limitations
- **Encryption write support**: `Document.encrypt()` configures encryption parameters but the underlying Rust library does not yet serialize the encryption dictionary to the PDF output. Reading encrypted PDFs works correctly.
- **Image extraction returns raw embedded streams**: `extract_images_from_pdf` extracts each embedded image as-is (e.g. a `DCTDecode` JPEG is written byte-for-byte). Image *preprocessing* — auto rotation-correction, contrast enhancement, denoise, upscaling, force-grayscale — is not available, because the build excludes the upstream `external-images` feature (and its `image`-crate dependency). This keeps extraction faithful and lossless; it does not silently return empty or stub results.
- **CPython only**: PyPy and GraalPy are not supported.
## License
MIT — see [LICENSE](LICENSE) for details.
TDQS
Scored across 12 tools
Each tool targets a distinct operation (creation workflow, metadata, text extraction, layout analysis, annotation, form management, page manipulation, security, conversion, structural analysis). Descriptions clearly differentiate overlapping areas like extract_text vs convert_pdf vs extract_entities.
All 12 tools follow a consistent verb_noun snake_case pattern (e.g., create_pdf, extract_text, secure_pdf). No mixed conventions or ambiguous names.
12 tools cover the full PDF lifecycle without bloat. Each tool has a clear purpose and the count is well-scoped for a PDF manipulation server.
The surface covers creation, reading, editing, conversion, security, and analysis. Minor gaps like lacking an explicit page deletion tool (handled via manipulate_pdf's extract_pages) or OCR, but core workflows are complete.