About pdfedit-mcp
pdfedit-mcp is an MCP server for editing text and form fields in existing PDFs. It preserves the document’s layout and fonts where available, and returns previews of changes alongside the edited file.
Key features
- 01Replace, delete, redact, or insert PDF text
- 02Fill PDF form fields
- 03Find text spans with page and font details
- 04Preview pages or selected regions
- 05Verify edits and roll back failed changes
Use cases
- 01Correct names or wording in a contract
- 02Redact text in an existing PDF
- 03Fill forms across documents using a template
MCP support
Exposes five PDF editing and inspection tools to MCP clients, including editing, batch jobs, text search, previews, and job results. The local package can work with local files; the hosted endpoint accepts PDF URLs.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcpclaude mcp add --transport http zp130221-pdfedit https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcp{"mcpServers":{"zp130221-pdfedit":{"type":"http","url":"https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcp"}}}Open in Cursor{"servers":{"zp130221-pdfedit":{"type":"http","url":"https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcp"}}}Open in VS Code{"mcpServers":{"zp130221-pdfedit":{"type":"http","url":"https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcp"}}}{"mcpServers":{"zp130221-pdfedit":{"serverUrl":"https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
5| Parameter | Type | Required | Description |
|---|---|---|---|
| file | string | Yes | The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. |
| edits | array | Yes | The edits to apply, in order, exactly as `POST /v1/edit` takes them. |
| options | object | No | |
| options.ocr | enum | No | (default: "auto")auto | on | off |
| options.font | object | No | |
| options.font.policy | enum | No | (default: "auto")auto | metric_only | exact_only |
| options.tidy | boolean | No | (default: true) |
| options.async | boolean | No | (default: false) |
| options.atomic | boolean | No | (default: true) |
| options.retain | boolean | No | (default: true) |
| options.strict | boolean | No | (default: false) |
| options.dry_run | boolean | No | (default: false) |
| options.preview | enum | No | (default: "changes")changes | pages | none |
| options.ocr_lang | array | No | |
| options.watermark | enum | No | (default: "auto")auto | on | off |
| options.webhook_url | any | No | |
| options.redact_style | enum | No | (default: "box")box | text |
| options.break_signature | boolean | No | (default: false) |
| options.redact_metadata | boolean | No | (default: false) |
| options.redact_verify_ocr | boolean | No | (default: true) |
| options.collateral_threshold | integer | No | (default: 8) |
| password | string | No | The document open password, when it has one. |
| output_path | string | No | Where to write the edited PDF (stdio mode only). Default: `<name>.edited.pdf` next to the input. |
Details
From the project README@niubai/pdfedit-mcp — edit existing PDFs from your agent
Five MCP tools over the PDF Edit API: replace, delete, redact, insert and form-fill text inside a PDF that already exists, keeping the original font, the original layout and everything you did not point at. Not "generate a new PDF that looks similar" — the same file, with the words you named changed, and a before/after picture of every change.
With
PDFEDIT_BASE_URLunset the server talks to the hosted API athttps://api.niubai.vip; set it to point at another deployment.
Try it in 30 seconds, no key
Without PDFEDIT_API_KEY the server runs on the keyless free tier: 20 pages a day
per IP, outputs watermarked TEST, local files included.
Claude Code
claude mcp add pdfedit -- npx -y @niubai/pdfedit-mcp
Cursor / VS Code / Claude Desktop — this server entry (or the buttons on the repository page):
{ "pdfedit": { "command": "npx", "args": ["-y", "@niubai/pdfedit-mcp"] } }
Then ask your agent: "In ./contract.pdf replace 'Acme Ltd' with 'Acme GmbH' and redact the IBAN." The edited PDF is written next to the original and a before/after picture of every change comes back. A free API key (200 pages a month, no watermark on ordinary documents) takes one email at https://api.niubai.vip.
Install
Claude Code — one line:
claude mcp add pdfedit -e PDFEDIT_API_KEY=sk_live_your_key -- npx -y @niubai/pdfedit-mcp
Cursor — the deeplink (Settings → MCP also takes the same JSON):
cursor://anysphere.cursor-deeplink/mcp/install?name=pdfedit&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBuaXViYWkvcGRmZWRpdC1tY3AiXSwiZW52Ijp7IlBERkVESVRfQVBJX0tFWSI6InNrX2xpdmVfeW91cl9rZXkifX0=
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"pdfedit": {
"command": "npx",
"args": ["-y", "@niubai/pdfedit-mcp"],
"env": {
"PDFEDIT_API_KEY": "sk_live_your_key"
}
}
}
}
No key? Leave env out: the keyless free tier is 20 pages/day per IP, outputs
watermarked TEST. Web clients that cannot run a local server (claude.ai) can add the
hosted endpoint instead; it takes PDF URLs only.
Local files versus URLs
This package runs on your machine, so every tool's file argument takes an absolute
(or ~) local path — it uploads the file for you and writes the edited PDF back next
to the input as <name>.edited.pdf — or an https:// URL, or an f_… file id from an
earlier call. The hosted Streamable-HTTP endpoint cannot read your disk and says so
(remote server cannot read local paths; pass a URL). PDFs never travel as MCP content
blobs either way; only up to three preview PNGs of the changed pages come back inline.
Tools
| Tool | What it does |
|---|---|
pdf_edit | Replace / delete / redact / insert / fill, one call, with previews. |
pdf_batch | The same edits once per record over one template. Returns a job id. |
pdf_find | Every span with page, bbox, font and span id; filter with the four match modes. |
pdf_preview | Render a page (or a region) as a PNG, with optional highlight boxes. |
pdf_job | The result of an async edit or a batch. |
Every schema is generated from the gateway's OpenAPI document, so an edits[] entry here
is exactly the one POST /v1/edit and the SDKs take — see
src/tools.ts.
What it will refuse, and why
The engine would rather hand back "no" with a reason than a file that looks edited and is not. Two kinds of refusal.
Verification gates — the edit was attempted and rolled back
Every change is written, then checked, then kept or undone. A group that fails any gate
is rolled back and reported in failed[]:
| Reason | What happened |
|---|---|
collateral_damage | Pixels changed outside the rewrite's own area — something else on the page moved. The page is restored byte-for-byte. |
readback_failed | Re-reading the edited rectangle did not return the text that was supposed to be there. |
does_not_fit | The replacement does not fit the space, and fit was not allowed to make it worse (fit: "strict"), or an insert would have landed on text that is already there. |
font_unavailable | options.font.policy: "exact_only" (or "metric_only") and neither the system nor the document has an acceptable face. |
redact_residual | After a redaction the string was still readable in the rect — the removal is refused rather than reported as done. |
no_target, field_not_editable, field_value_invalid, field_value_mismatch | The form field does not exist, is read-only, rejects the value, or did not read back as written. |
With options.atomic (the default) any single failure fails the whole request and no
file is produced; options.strict does the same for an edit that simply did not match.
An unmatched edit comes back with fuzzy suggestions ("did you mean Acme, Inc.?")
rather than a silent no-op.
Known-unsupported — rejected up front as invalid_request
What the API refuses before touching the document:
- Right-to-left text (Arabic, Hebrew) in
find. Everything after the matcher is left-to-right: lines are assembled left edge first and replacements are laid out rightwards, so RTL would be matched against a visually reversed line and written in the wrong direction with nothing downstream able to notice. redacton text that only exists as scanned pixels. The word is the image; covering it leaves it in the file, so the op's promise cannot be kept.styletogether withfit: "reflow". A reflow re-sets the whole paragraph in the document's own face, so the matched phrase no longer has a run of its own to style.fit: "reflow"onredactorform.options.preview: "pages"(whole-page previews);"changes"and"none"work.- Unknown ops.
Known limits — supported, with a caveat worth knowing
- A raster OCR write produces an image, not text. Where a scanned line has no underlying text to clone, the new words are drawn as pixels: correct on the page, verified by re-rendering, but not selectable or searchable afterwards.
- A redaction on a scan is verified by the text layer and the pixel scrub, not by recognising the output again.
- Text positioned relatively — matplotlib writes each tick relative to the previous one — can make an unrelated element move hundreds of points away. The engine sees that as collateral damage and rolls the change back; the page stays correct, the edit does not happen. One corpus file does this on purpose as a regression.
- Fonts. A replacement is set in the document's own font where the file embeds enough
of it (any page's subset of that font counts), in a metric-compatible twin where it
does not (Arial, Helvetica, Times New Roman, Courier New, Calibri, Cambria, Georgia,
Segoe UI, Palatino, Book Antiqua, Bookman, Century Gothic, Avant Garde, New Century
Schoolbook and Arial Narrow all have one: same widths, so the line ends where it did),
and in a look-alike of the same class as a last resort. The result always says which
(
font_source,font_used), the summary spells outapprox_font_usedwhen the last resort was taken, andoptions.font.policy: "metric_only"refuses it outright, so "it looks slightly different" is a value you can read rather than a surprise. flattenon radio groups and comboboxes depends on the appearance the file carries; where it declines, the field is left live andflatten_skippedis warned instead of failing.- Encrypted documents need the open password (
password); edits are not attempted without it.
More
- Quick start — install per client, what to say to the agent, what comes back. The canonical usage doc; this README stays short on purpose.
- API reference — every field of
edits[],optionsand the response. - Examples — ten before/after pairs, each with the request that produced it.
- Repository — this server, the SDKs and the hosted endpoint's source.
MIT. The engine behind the API is AGPL-3.0; the API serves its complete source to its
users at GET /source (any key, a free test key included; the hosted MCP endpoint relays
it at https://pdfedit-mcp-remote.niubai-pdfedit-remote-worker.workers.dev/source).
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.