{
  "markdown": "<!-- mcp-name: io.github.bzsanti/oxidize-pdf-mcp -->\n# oxidize-pdf\n\n<!-- mcp-name: io.github.bzsanti/oxidize-pdf-mcp -->\n\n[![PyPI version](https://img.shields.io/pypi/v/oxidize-pdf)](https://pypi.org/project/oxidize-pdf/)\n[![CI](https://github.com/bzsanti/oxidize-python/actions/workflows/ci.yml/badge.svg)](https://github.com/bzsanti/oxidize-python/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Python](https://img.shields.io/pypi/pyversions/oxidize-pdf)](https://pypi.org/project/oxidize-pdf/)\n[![Typed](https://img.shields.io/badge/typing-typed-green)](https://github.com/bzsanti/oxidize-python)\n[![MCP](https://img.shields.io/badge/MCP-compatible-purple)](https://modelcontextprotocol.io/)\n\n[![oxidize-python MCP server](https://glama.ai/mcp/servers/bzsanti/oxidize-python/badges/card.svg)](https://glama.ai/mcp/servers/bzsanti/oxidize-python)\n\n**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.\n\nNo C dependencies. No Java. No subprocess calls.\n\n## Installation\n\n```bash\npip install oxidize-pdf            # Core library\npip install \"oxidize-pdf[mcp]\"     # + MCP server for AI agents\n```\n\n**Platforms:** Linux (x86_64, aarch64) | macOS (x86_64, Apple Silicon) | Windows (x86_64)\n**Requires:** Python 3.10+\n\n## Why oxidize-pdf?\n\n| | oxidize-pdf | Pure-Python libs | C/Java wrappers |\n|---|---|---|---|\n| **Performance** | Native (compiled Rust) | Interpreted | Native but heavy |\n| **Dependencies** | Zero | Varies | Poppler, Java, Ghostscript |\n| **Memory safety** | Rust ownership model | GC-dependent | Manual / GC |\n| **Type stubs** | Full (mypy/pyright) | Partial | Rare |\n| **AI-ready (MCP)** | Built-in | No | No |\n\n---\n\n## MCP Server\n\nGive your AI agent full PDF capabilities in one line:\n\n```bash\noxidize-mcp\n```\n\nThe built-in [Model Context Protocol](https://modelcontextprotocol.io/) server exposes **12 tools**, **6 resources**, and **5 prompts** — compatible with Claude, GPT, and any MCP client.\n\n### Claude Desktop integration\n\nAdd to your `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"oxidize-pdf\": {\n      \"command\": \"oxidize-mcp\",\n      \"env\": {\n        \"OXIDIZE_WORKSPACE\": \"/path/to/your/pdfs\"\n      }\n    }\n  }\n}\n```\n\n### GitHub Copilot (VS Code) integration\n\nCopilot's agent mode speaks MCP. Add `.vscode/mcp.json` to your workspace:\n\n```json\n{\n  \"servers\": {\n    \"oxidize-pdf\": {\n      \"command\": \"oxidize-mcp\",\n      \"env\": {\n        \"OXIDIZE_WORKSPACE\": \"/path/to/your/pdfs\"\n      }\n    }\n  }\n}\n```\n\nOpen the Chat view, switch to **Agent** mode, and the 12 PDF tools appear in\nthe tool picker. (The same block also works under the `mcp.servers` key in your\nuser `settings.json` if you prefer a global install.)\n\n### OpenAI Agents SDK integration\n\nThe [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/mcp/)\nspawns the server over stdio and exposes its tools to an agent:\n\n```python\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStdio\n\nasync with MCPServerStdio(\n    params={\"command\": \"oxidize-mcp\", \"env\": {\"OXIDIZE_WORKSPACE\": \"/path/to/your/pdfs\"}},\n    cache_tools_list=True,\n) as server:\n    agent = Agent(\n        name=\"PDF assistant\",\n        instructions=\"Use the oxidize-pdf tools to inspect and manipulate PDFs.\",\n        mcp_servers=[server],\n    )\n    result = await Runner.run(agent, \"How many pages does report.pdf have?\")\n    print(result.final_output)\n```\n\nA runnable version is in [`examples/openai_agents_quickstart.py`](examples/openai_agents_quickstart.py).\n\n> Both integrations run the server **locally over stdio**, so its tools operate\n> on PDFs in the configured workspace directory. Remote/hosted use (e.g. the\n> OpenAI Responses API hosted MCP tool) needs an HTTP transport and is not yet\n> exposed.\n\n### Available tools\n\n| Tool | What it does |\n|------|-------------|\n| `read_pdf` | Read metadata — page count, version, encryption status, title, author |\n| `extract_text` | Extract text from all pages or a specific page |\n| `convert_pdf` | Convert to markdown, chunks, or RAG-optimized format |\n| `create_pdf` | Create a new PDF with optional metadata |\n| `save_pdf` | Save a session to disk, with optional encryption |\n| `add_content` | Add pages, text, and graphics to a session |\n| `annotate_pdf` | Add text annotations and highlights |\n| `manipulate_pdf` | Split, merge, rotate, extract pages, reverse, overlay |\n| `manage_forms` | Create, fill, read, and validate form fields |\n| `secure_pdf` | Encrypt, check permissions, verify signatures |\n| `extract_entities` | Extract structured entities from pages |\n| `analyze_pdf` | Validate structure, detect corruption, check PDF/A compliance |\n\nThe server also exposes **resources** (session data, capabilities, version info) and **prompts** (guided workflows for summarization, data extraction, form filling, and more).\n\n### Configuration\n\n```bash\nOXIDIZE_WORKSPACE=/path/to/pdfs oxidize-mcp\n```\n\nThe server is configured entirely through environment variables:\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `OXIDIZE_WORKSPACE` | `~/Documents/oxidize-mcp` | Sandbox root; all paths must resolve inside it. |\n| `OXIDIZE_ALLOWED_PATHS` | _(none)_ | Comma-separated extra directories allowed outside the workspace. |\n| `OXIDIZE_MAX_FILE_SIZE_MB` | `100` | Reject input PDFs larger than this on disk. |\n| `OXIDIZE_MAX_PAGES` | `10000` | Reject documents with more pages than this before any extraction work. |\n| `OXIDIZE_MAX_OUTPUT_BYTES` | `10485760` | Cap the serialized size of a tool's JSON response (10 MB). |\n| `OXIDIZE_MAX_SESSIONS` | `10` | Maximum concurrent stateful PDF-creation sessions. |\n| `OXIDIZE_MAX_SESSION_BYTES` | `10485760` | Cap the content a single session may accumulate (10 MB). |\n| `OXIDIZE_SESSION_TIMEOUT` | `3600` | Session expiry, in seconds. |\n\nResource caps (`OXIDIZE_MAX_*`) protect the server from a large or malicious\nPDF: oversized documents are rejected up front and tool responses are bounded\nrather than serialized unbounded. Exceeding a cap returns an error with code\n`RESOURCE_LIMIT`.\n\nOr start programmatically:\n\n```python\nfrom oxidize_pdf.mcp.server import run\nrun()\n```\n\n---\n\n## Python API\n\n### Create a PDF\n\n```python\nfrom oxidize_pdf import Document, Page, Font, Color\n\ndoc = Document()\ndoc.set_title(\"My Document\")\ndoc.set_author(\"Jane Doe\")\n\npage = Page.a4()\npage.set_font(Font.HELVETICA, 24.0)\npage.set_text_color(Color.black())\npage.text_at(72.0, 750.0, \"Hello from oxidize-pdf!\")\n\npage.set_font(Font.TIMES_ROMAN, 12.0)\npage.text_at(72.0, 700.0, \"Generated with Python + Rust.\")\n\ndoc.add_page(page)\ndoc.save(\"output.pdf\")\n```\n\n### Parse an existing PDF\n\n```python\nfrom oxidize_pdf import PdfReader\n\nreader = PdfReader.open(\"document.pdf\")\nprint(f\"Pages: {reader.page_count}, Version: {reader.version}\")\n\nfor i, text in enumerate(reader.extract_text()):\n    print(f\"--- Page {i + 1} ---\")\n    print(text)\n```\n\n### Operations\n\n```python\nfrom oxidize_pdf import split_pdf, merge_pdfs, rotate_pdf, extract_pages\n\nsplit_pdf(\"input.pdf\", \"output_dir/\")                       # Split into individual pages\nmerge_pdfs([\"part1.pdf\", \"part2.pdf\"], \"merged.pdf\")         # Merge multiple PDFs\nrotate_pdf(\"input.pdf\", \"rotated.pdf\", 90)                   # Rotate all pages\nextract_pages(\"input.pdf\", \"subset.pdf\", [0, 2, 4])          # Extract specific pages\n```\n\n### Graphics\n\n```python\nfrom oxidize_pdf import Document, Page, Color\n\ndoc = Document()\npage = Page.a4()\n\npage.set_fill_color(Color.hex(\"#3498db\"))\npage.draw_rect(72.0, 700.0, 200.0, 100.0)\npage.fill()\n\npage.set_stroke_color(Color.red())\npage.set_line_width(2.0)\npage.draw_circle(300.0, 500.0, 50.0)\npage.stroke()\n\ndoc.add_page(page)\ndoc.save(\"graphics.pdf\")\n```\n\n### Types\n\n```python\nfrom oxidize_pdf import Color, Point, Rectangle, Margins, Font\n\n# Colors\nColor.rgb(1.0, 0.0, 0.0)          # RGB\nColor.hex(\"#ff6600\")               # Hex\nColor.cmyk(0.0, 1.0, 1.0, 0.0)   # CMYK\n\n# Geometry\nPoint(72.0, 720.0)\nRectangle.from_xywh(72.0, 72.0, 468.0, 648.0)\nMargins.uniform(72.0)\n\n# Fonts — all 14 standard PDF fonts\nFont.HELVETICA    # Font.HELVETICA_BOLD\nFont.TIMES_ROMAN  # Font.TIMES_BOLD\nFont.COURIER      # Font.COURIER_BOLD\n```\n\n### Error handling\n\n```python\nfrom oxidize_pdf import PdfReader, PdfError, PdfIoError, PdfParseError\n\ntry:\n    reader = PdfReader.open(\"missing.pdf\")\nexcept PdfIoError as e:\n    print(f\"I/O error: {e}\")\nexcept PdfParseError as e:\n    print(f\"Parse error: {e}\")\nexcept PdfError as e:\n    print(f\"PDF error: {e}\")\n```\n\nException hierarchy: `PdfError` > `PdfIoError`, `PdfParseError`, `PdfEncryptionError`, `PdfPermissionError`\n\n## MCP Server\n\noxidize-pdf includes an [MCP](https://modelcontextprotocol.io/) server that exposes PDF capabilities to AI assistants like Claude. Install with the `mcp` extra:\n\n```bash\npip install oxidize-pdf[mcp]\n```\n\n### Claude Desktop\n\nAdd this to your `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"oxidize-pdf\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"oxidize-pdf[mcp]\", \"oxidize-mcp\"]\n    }\n  }\n}\n```\n\n### Claude Code\n\n```bash\nclaude mcp add oxidize-pdf -- uvx --from \"oxidize-pdf[mcp]\" oxidize-mcp\n```\n\n### Available tools\n\n| Tool | Description |\n|---|---|\n| `read_pdf` | Open a PDF and get metadata (pages, version, encryption) |\n| `extract_text` | Extract text content from PDF pages |\n| `convert_pdf` | Convert between PDF versions |\n| `analyze_pdf` | Analyze structure, fonts, images, and compliance |\n| `extract_entities` | Extract images and digital signatures |\n| `manipulate_pdf` | Split, merge, rotate, extract, and reorder pages |\n| `annotate_pdf` | Add text annotations, highlights, and stamps |\n| `manage_forms` | Create, fill, and read PDF form fields |\n| `secure_pdf` | Encrypt, decrypt, and set document permissions |\n| `create_pdf` | Create a new PDF document with pages |\n| `add_pdf_content` | Add text, shapes, and images to pages |\n| `save_pdf` | Save the document to file or bytes |\n\n### Resources\n\n- `oxidize://fonts` — Available built-in PDF fonts\n- `oxidize://page-sizes` — Standard page sizes with dimensions\n- `oxidize://capabilities` — Server capabilities and tool listing\n- `oxidize://version` — Version information\n- `oxidize://workspace` — PDF files in the workspace directory\n- `oxidize://session/{id}` — Session data by ID\n\n## Known limitations\n\n- **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.\n- **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.\n- **CPython only**: PyPy and GraalPy are not supported.\n\n## License\n\nMIT — see [LICENSE](LICENSE) for details.\n",
  "bytes": 11324,
  "sha": "02f760cd31204f940fcdb525db421191a608c8c85056a4e402635483a86cc7fc",
  "repo_slug": "bzsanti/oxidize-python",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_bzsanti_oxidize_pdf_mcp_ae21c89e/readme"
}