{"_id":"@aryanbv/pdf-edit-mcp","_rev":"3-f9b0caa0cb53af5c7e3411888c7a8883","name":"@aryanbv/pdf-edit-mcp","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@aryanbv/pdf-edit-mcp","version":"0.1.0","keywords":["mcp","mcp-server","pdf","pdf-edit","pdf-replace","pdf-find","text-editing","format-preserving","model-context-protocol","claude","typescript","ai-tools"],"author":{"name":"Aryan B V"},"license":"MIT","_id":"@aryanbv/pdf-edit-mcp@0.1.0","maintainers":[{"name":"aryanbv","email":"aryansalian5678@gmail.com"}],"homepage":"https://github.com/AryanBV/pdf-edit-mcp","bugs":{"url":"https://github.com/AryanBV/pdf-edit-mcp/issues"},"bin":{"pdf-edit-mcp":"dist/index.js"},"dist":{"shasum":"36e3b7785efcdc7d2d3895b001bb217a09a6182b","tarball":"https://registry.npmjs.org/@aryanbv/pdf-edit-mcp/-/pdf-edit-mcp-0.1.0.tgz","fileCount":8,"integrity":"sha512-2JN0oXh+1vMnx1hYvKGqgjiXratCNLZhcb2ETu+1fdgv6V3usxCX8+UqT9MHUh04ZYL42qKB8fZvojf46FkCbQ==","signatures":[{"sig":"MEYCIQCsjgiiHzvVh4GVHBL6BiM8Gx5+nAVuDF0nrev2ogf2aAIhAP2I7aOPeLUaVK4w9DfSGstQINyunL514bp59DgB4xMs","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":151162},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"gitHead":"75c39752c5a4a15892d752005dcca9f61e62ad82","scripts":{"dev":"tsc --watch","test":"vitest run","audit":"npm audit --production --audit-level=high","build":"tsc","start":"node dist/index.js","inspect":"npx @modelcontextprotocol/inspector node dist/index.js","test:watch":"vitest"},"_npmUser":{"name":"aryanbv","email":"aryansalian5678@gmail.com"},"repository":{"url":"git+https://github.com/AryanBV/pdf-edit-mcp.git","type":"git"},"_npmVersion":"10.9.0","description":"MCP server for format-preserving PDF text editing — find, replace, and batch-edit text in existing PDFs while preserving fonts, layout, and visual fidelity.","directories":{},"_nodeVersion":"23.1.0","dependencies":{"zod":"^3.25.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.1.0","typescript":"^5.7.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/pdf-edit-mcp_0.1.0_1775903407893_0.9111651464430128","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Moved to Python: pip install pdf-edit-mcp (uvx pdf-edit-mcp). https://pypi.org/project/pdf-edit-mcp/"},"0.1.1":{"name":"@aryanbv/pdf-edit-mcp","version":"0.1.1","keywords":["mcp","mcp-server","pdf","pdf-edit","pdf-replace","pdf-find","text-editing","format-preserving","model-context-protocol","claude","typescript","ai-tools"],"author":{"name":"Aryan B V"},"license":"MIT","_id":"@aryanbv/pdf-edit-mcp@0.1.1","maintainers":[{"name":"aryanbv","email":"aryansalian5678@gmail.com"}],"homepage":"https://github.com/AryanBV/pdf-edit-mcp","bugs":{"url":"https://github.com/AryanBV/pdf-edit-mcp/issues"},"bin":{"pdf-edit-mcp":"dist/index.js"},"dist":{"shasum":"97d777fa75140016449d641bfebf92cdfceef0d5","tarball":"https://registry.npmjs.org/@aryanbv/pdf-edit-mcp/-/pdf-edit-mcp-0.1.1.tgz","fileCount":11,"integrity":"sha512-tH2OTK/z1q0906RiASi1iN6ujDsYB82+6KEfL5z3XGV+UFoAV7KI6YAoAcxmbUrH48Ut5G1wNaXYEo/L135RhA==","signatures":[{"sig":"MEUCIF/CKQv8y4sBloFv5kk2ouI41lL8IZp3qFu6mPtz1MHDAiEAiRRiOfUumdVGasWG+e+wb6+uIGEDP5rRGO5kIMSB1kA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":194075},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"gitHead":"0551138ad0784cc806e135d95db3589672a1169c","scripts":{"dev":"tsc --watch","test":"vitest run","audit":"npm audit --production --audit-level=high","build":"tsc","start":"node dist/index.js","inspect":"npx @modelcontextprotocol/inspector node dist/index.js","test:unit":"vitest run tests/validation.test.ts tests/security.test.ts","test:watch":"vitest","prepublishOnly":"npm run build && npm test","test:integration":"vitest run tests/bridge.test.ts"},"_npmUser":{"name":"aryanbv","email":"aryansalian5678@gmail.com"},"repository":{"url":"git+https://github.com/AryanBV/pdf-edit-mcp.git","type":"git"},"_npmVersion":"10.9.0","description":"MCP server for format-preserving PDF text editing — find, replace, and batch-edit text in existing PDFs while preserving fonts, layout, and visual fidelity.","directories":{},"_nodeVersion":"23.1.0","dependencies":{"zod":"^3.25.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.1.0","typescript":"^5.7.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/pdf-edit-mcp_0.1.1_1777744195489_0.8930652724129806","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Moved to Python: pip install pdf-edit-mcp (uvx pdf-edit-mcp). https://pypi.org/project/pdf-edit-mcp/"}},"time":{"created":"2026-04-11T10:30:07.787Z","modified":"2026-06-02T11:50:09.385Z","0.1.0":"2026-04-11T10:30:08.073Z","0.1.1":"2026-05-02T17:49:55.617Z"},"bugs":{"url":"https://github.com/AryanBV/pdf-edit-mcp/issues"},"author":{"name":"Aryan B V"},"license":"MIT","homepage":"https://github.com/AryanBV/pdf-edit-mcp","keywords":["mcp","mcp-server","pdf","pdf-edit","pdf-replace","pdf-find","text-editing","format-preserving","model-context-protocol","claude","typescript","ai-tools"],"repository":{"url":"git+https://github.com/AryanBV/pdf-edit-mcp.git","type":"git"},"description":"MCP server for format-preserving PDF text editing — find, replace, and batch-edit text in existing PDFs while preserving fonts, layout, and visual fidelity.","maintainers":[{"name":"aryanbv","email":"aryansalian5678@gmail.com"}],"readme":"# pdf-edit-mcp\n\nMCP server for editing text in existing PDFs through content-stream surgery. Targets fidelity preservation (original font, exact position, in-place operators) and reports — honestly — when fidelity has to break.\n\n[![npm version](https://img.shields.io/npm/v/@aryanbv/pdf-edit-mcp)](https://www.npmjs.com/package/@aryanbv/pdf-edit-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![CI](https://github.com/AryanBV/pdf-edit-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/AryanBV/pdf-edit-mcp/actions/workflows/ci.yml)\n![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)\n![Python](https://img.shields.io/badge/python-%3E%3D3.12-blue)\n\n## How it works\n\nMost PDF editors use a redact-and-replace approach — they white out the original text and stamp new text on top, usually with a substitute font. The result looks different from the original.\n\npdf-edit-mcp takes a different approach. It modifies the original PDF content stream operators directly, preserving the exact font, size, color, and position of the text being edited — when the embedded font already contains the glyphs you need.\n\n| | Traditional approach | pdf-edit-mcp |\n|---|---|---|\n| **Method** | Redact old text, stamp new text | Modify content stream operators in place |\n| **Font** | Substituted (often Helvetica) | Original font when possible; metric-equivalent fallback (e.g. Carlito for Calibri) when not |\n| **Position** | Re-calculated | Exact original coordinates |\n| **Quality feedback** | None | FidelityReport on every edit (font_substituted, glyphs_missing, overflow_detected, warnings) |\n\nPowered by [pdf-edit-engine](https://github.com/AryanBV/pdf-edit-engine) — a Python library for PDF content stream surgery with two-tier font subset extension.\n\n## When fidelity is exact, and when it isn't\n\nThis matters more than the headline claim. The engine has three fidelity tiers, and every edit's `FidelityReport` tells you which one fired:\n\n- **Tier 1 — exact** (`font_preserved=true`, `font_substituted=null`): the embedded font already had every glyph the replacement needs. Output is byte-identical at the operator layer.\n- **Tier 1.5 — in-place injection** (`font_preserved=true`, glyph appended to embedded font): glyph wasn't in the embedded font but was in your system font with the same `unitsPerEm`. The original CIDs are preserved; only new glyphs are appended at fresh GIDs. Visual: indistinguishable from Tier 1.\n- **Metric-equivalent fallback** (`font_preserved=false`, `font_substituted=\"Carlito-Regular\"` or similar): the original font isn't installed system-wide, so an open-source font with matching metrics substitutes for the new glyphs. Visual: very close but not pixel-perfect; spacing is right because metrics match.\n\nWhat straight-up fails (the engine raises, the MCP returns a structured error):\n- The font is CFF / Type 1 / Type 3 (`FontNotFoundError` — TrueType only for Tier 1.5 today).\n- The `unitsPerEm` of the system font differs from the embedded font (rescaling out of scope).\n- The replacement is wider than the available bbox AND there's no room to reflow downward (`OverflowError` surfaced via `EditResult.warnings`).\n- Multi-codepoint emoji or scripts the system fonts don't carry.\n\nIf you need fidelity guarantees for a specific PDF, run `pdf_analyze_subset` first to see what tier you'll land in.\n\n## Features\n\n- **38 tools** across 7 categories (reading, text editing, block ops, section ops, annotations, document manipulation, metadata & security)\n- **3 built-in MCP prompts** that guide the editing workflow step by step\n- **Fidelity reporting** on every edit: `font_preserved`, `font_substituted`, `overflow_detected`, `reflow_applied`, `glyphs_missing`, plus a `warnings` list (auto-includes overflow notices)\n- **`dry_run` preview** on `pdf_replace_text`, `pdf_replace_single`, `pdf_batch_replace` — return the FidelityReport without writing the output PDF, so you can verify font/glyph coverage before committing\n- **Per-page filtering** on `pdf_find_text`, `pdf_get_text`, `pdf_get_fonts` — restrict reads to a single 0-indexed page on multi-page PDFs\n- **Layout overrides** on `pdf_replace_block` and `pdf_batch_replace_block` — explicit `line_height` and `section_gap` for uniform spacing across sibling sections\n- **Batch operations** — up to 500 find-and-replace edits per call, up to 50 block replacements per page, with auto-verification on the output\n- **Section intelligence** — detects document structure by font hierarchy, swaps sections by fuzzy title match (raises on ambiguous match rather than silently picking)\n- **Atomic write** — section-swap operations write to a temp file and rename only on full success; failures leave your output path untouched\n- **Engine-version pin enforced at startup** — bridge hard-fails if `pdf-edit-engine < 0.1.2` is installed, so missing fidelity fields can't masquerade as `null`\n- **Structured error codes** — engine errors map to specific JSON-RPC codes (`-32001` stale match, `-32002` encoding, `-32003` reflow, `-32004` font-not-found) with embedded recovery hints\n- **Runs entirely local** — no external APIs, no network calls, no API keys\n\n## Quick Start\n\n### Prerequisites\n\n- **Node.js** 20+\n- **Python** 3.12+\n- **pdf-edit-engine** ≥ 0.1.2: `pip install \"pdf-edit-engine>=0.1.2\"`\n\n### Claude Desktop\n\nAdd to your `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"pdf-edit-mcp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aryanbv/pdf-edit-mcp\"]\n    }\n  }\n}\n```\n\n### Claude Code\n\n```bash\nclaude mcp add pdf-edit-mcp -- npx -y @aryanbv/pdf-edit-mcp\n```\n\n### Other MCP clients (Cursor, Windsurf, etc.)\n\n```bash\nnpx -y @aryanbv/pdf-edit-mcp\n```\n\n### Custom Python path\n\nIf `python` isn't in your PATH or you need a specific version:\n\n```json\n{\n  \"mcpServers\": {\n    \"pdf-edit-mcp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aryanbv/pdf-edit-mcp\"],\n      \"env\": {\n        \"PDF_EDIT_PYTHON\": \"/path/to/python3.12\"\n      }\n    }\n  }\n}\n```\n\n## Tools\n\n### Reading & Analysis\n\n| Tool | Description |\n|------|-------------|\n| `pdf_inspect` | Complete document overview — text, fonts, paragraphs, annotations in one call. Start here before editing. |\n| `pdf_get_text` | Extract all text from a PDF |\n| `pdf_find_text` | Find all occurrences of a string with page numbers and bounding box positions |\n| `pdf_get_fonts` | List fonts with encoding type, glyph count, PostScript name, subset status |\n| `pdf_get_text_layout` | Get every text block with exact position, font, and size |\n| `pdf_extract_bbox_text` | Extract text from a bounding box region with gap-aware joining |\n| `pdf_detect_paragraphs` | Detect paragraph boundaries with bounding boxes on a page |\n| `pdf_detect_sections` | Analyze document structure — section tree with titles, bounding boxes, and text |\n| `pdf_analyze_subset` | Check if an embedded font can render specific characters before editing |\n\n### Text Editing\n\n| Tool | Description |\n|------|-------------|\n| `pdf_replace_text` | Replace all occurrences of a string (names, dates, typos, labels) |\n| `pdf_replace_single` | Replace one specific occurrence by match index |\n| `pdf_batch_replace` | Multiple find-and-replace edits in one atomic operation (up to 500 edits) |\n\n### Block Operations\n\n| Tool | Description |\n|------|-------------|\n| `pdf_replace_block` | Replace all content within a bounding box with new text |\n| `pdf_batch_replace_block` | Replace content in multiple bounding boxes atomically with cumulative shift tracking |\n| `pdf_insert_text_block` | Insert text at a position, shift existing content down to make room |\n| `pdf_delete_block` | Delete content in a bounding box, optionally close the gap |\n\n### Section Operations\n\n| Tool | Description |\n|------|-------------|\n| `pdf_swap_sections` | Swap two sections by fuzzy title match — re-renders all siblings for uniform spacing |\n| `pdf_replace_section` | Replace a section's entire content by fuzzy title match |\n\n### Annotations & Links\n\n| Tool | Description |\n|------|-------------|\n| `pdf_get_annotations` | List all annotations with positions, types, and URLs |\n| `pdf_add_annotation` | Add a link annotation at a position on a page |\n| `pdf_update_annotation` | Update a link annotation's target URL |\n| `pdf_delete_annotation_v2` | Delete an annotation by page and index |\n| `pdf_move_annotation` | Move an annotation to a new position |\n| `pdf_add_hyperlink` | Add a clickable hyperlink to a page region |\n| `pdf_add_highlight` | Add a highlight annotation with QuadPoints |\n| `pdf_flatten_annotations` | Flatten all annotations into page content (non-editable) |\n\n### Document Manipulation\n\n| Tool | Description |\n|------|-------------|\n| `pdf_merge` | Merge multiple PDFs into one document |\n| `pdf_split` | Split a PDF into individual page files |\n| `pdf_reorder_pages` | Reorder pages by 0-indexed page number array |\n| `pdf_rotate_pages` | Rotate pages by 90, 180, or 270 degrees |\n| `pdf_delete_pages` | Delete specific pages (0-indexed) |\n| `pdf_crop_pages` | Crop all pages to a bounding box |\n| `pdf_add_watermark` | Overlay a watermark PDF on all pages |\n\n### Metadata & Security\n\n| Tool | Description |\n|------|-------------|\n| `pdf_edit_metadata` | Edit title, author, subject, creator, producer |\n| `pdf_add_bookmark` | Add a navigation bookmark pointing to a page |\n| `pdf_encrypt` | Encrypt with owner and user passwords |\n| `pdf_decrypt` | Decrypt a password-protected PDF |\n| `pdf_fill_form` | Fill form fields by name-value pairs |\n\n## Workflows\n\nThree built-in MCP prompts guide the editing process.\n\n### `comprehensive-pdf-edit`\n\nFor structural changes — section swaps, rewrites, multi-field updates:\n\n1. **Inspect** — Call `pdf_inspect` to get the full document overview\n2. **Understand structure** — Use `pdf_detect_sections` for section tree, `pdf_find_text` for simple text matches, or `pdf_get_text_layout` for raw block positions\n3. **Pre-check** — Call `pdf_analyze_subset` if replacement text has unusual characters (bullets, em-dashes, non-Latin scripts)\n4. **Execute** — Use `pdf_batch_replace` for text changes, `pdf_swap_sections` or `pdf_replace_section` for structural changes, then `pdf_update_annotation` if link URLs changed\n5. **Verify** — Call `pdf_get_text` on the output. Check for duplicates, missing content, and spurious spaces\n\n### `section-swap`\n\nFor swapping two sections by name:\n\n1. Call `pdf_detect_sections` to get the section tree\n2. Identify both sections by title match\n3. Call `pdf_batch_replace_block` with **all** sibling sections (not just the two being swapped) — unchanged siblings get their original text for uniform spacing\n4. Verify with `pdf_get_text`\n\n### `quick-pdf-edit`\n\nFor simple text changes — typos, dates, names:\n\n1. Call `pdf_find_text` to locate the text\n2. Call `pdf_replace_text` or `pdf_replace_single`\n3. Check `font_preserved` in the fidelity report\n\n## Architecture\n\n```\nAI Agent (Claude, GPT, etc.)\n    ↓  MCP protocol (stdio)\nindex.ts — TypeScript MCP server\n    ↓  JSON-RPC 2.0 over stdin/stdout\nbridge.py — long-running Python subprocess\n    ↓  direct import\npdf-edit-engine — Python library (pikepdf + fonttools + pdfminer)\n```\n\n- The TypeScript server spawns `bridge.py` once at startup and keeps it alive for all tool calls, avoiding Python startup overhead on every request.\n- All inputs are validated by Zod schemas before reaching the Python layer.\n- `stdout` is the IPC channel — all logging goes to `stderr`.\n\n## Tested PDF generators\n\n| Generator | Encoding | Character agreement | Notes |\n|-----------|----------|-------------------|-------|\n| Chrome (Print to PDF) | Identity-H | 100% | Narrow font subsets exercise Tier 1.5 in-place glyph injection |\n| Google Docs export | Identity-H | 100% | |\n| Microsoft Word | Identity-H (Calibri) | 100% with Carlito metric-equivalent installed | `font_substituted` set when fallback fires |\n| reportlab (Python) | WinAnsi | 100% | Synthetic test fixture |\n\n## Limitations\n\nWhat v0.1.1 does **not** support:\n\n- **Cross-page reflow** — text expanding past a page boundary is not redistributed; you'll see an `overflow_detected: true` and a warning\n- **CFF / Type 1 / Type 3 fonts** — Tier 1.5 in-place glyph injection is TrueType only (`FontFile2` ↔ `glyf` table). Edits that need new glyphs in a CFF font return `FontNotFoundError` with code `-32004`\n- **`unitsPerEm` mismatch** — if the embedded font and your installed system font use different `unitsPerEm`, glyph rescaling is out of scope; the engine raises rather than ship distorted output\n- **Image editing or generation** — text-only\n- **Table structure detection** — text and bbox extraction work, but no table semantics\n- **Encodings beyond Identity-H and WinAnsi** — `MacRoman` and custom `/Differences` are decoded for reading but not exercised by the test fixtures\n- **Right-to-left text** — bidi reordering is not handled\n- **Multi-codepoint emoji / complex script glyphs** that aren't in your system fonts — recorded as `glyphs_missing` in the FidelityReport\n\n## Error codes\n\nJSON-RPC error codes the bridge can return (in addition to standard `-32600`/`-32601`/`-32602`):\n\n| Code | Class | Hint |\n|---|---|---|\n| `-32000` | `PDFEditError` (generic) | Inspect the message for context |\n| `-32001` | `OperatorError` | TextMatch is stale — re-run `pdf_find_text` and retry |\n| `-32002` | `EncodingError` | Run `pdf_analyze_subset` to see which characters can't encode |\n| `-32003` | `ReflowError` | Replacement may be too wide for the bbox — try shorter text |\n| `-32004` | `FontNotFoundError` | Install the original font system-wide, or accept metric-equivalent fallback |\n| `-32603` | Internal error | Bug — please report at the issue tracker |\n\n## Troubleshooting\n\n**\"Python not found\"** — Set `PDF_EDIT_PYTHON` to your Python 3.12+ path (see [Custom Python path](#custom-python-path)).\n\n**\"No module named pdf_edit_engine\"** — Install the engine: `pip install pdf-edit-engine`\n\n**Bridge process crashes on startup** — Verify Python >=3.12 (`python --version`) and check stderr for import errors.\n\n**Characters not rendering after replacement** — Call `pdf_analyze_subset` before editing to check if the embedded font supports the new characters.\n\n**\"Path must be absolute\"** — All `pdf_path` and `output_path` values must be absolute paths ending in `.pdf`.\n\n## Development\n\n```bash\ngit clone https://github.com/AryanBV/pdf-edit-mcp.git\ncd pdf-edit-mcp\nnpm install && npm run build\n```\n\n```bash\nnpm test              # validation + security + integration tests\nnpm run inspect       # launch MCP Inspector for manual testing\nnpm run audit         # security audit\n```\n\nIntegration tests require Python 3.12+, pdf-edit-engine, and reportlab (`pip install pdf-edit-engine reportlab`).\n\nCI runs in two stages: unit tests (TypeScript validation and security) → integration tests (Python bridge with generated fixtures).\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md"}