{
  "markdown": "# pdfnative-mcp\n\n> **MCP server for PDF generation, PDF/A archival, PAdES signing with long-term validation, AcroForms, merge/split, encryption and layout preview** — 28 tools on the [pdfnative](https://github.com/Nizoka/pdfnative) engine (zero-dependency, ISO 32000-1 compliant), for Claude Desktop, Cursor, ChatGPT and any Model Context Protocol client.\n\n[![npm version](https://img.shields.io/npm/v/pdfnative-mcp.svg?logo=npm&color=cb3837)](https://www.npmjs.com/package/pdfnative-mcp)\n[![npm downloads](https://img.shields.io/npm/dm/pdfnative-mcp.svg?logo=npm)](https://www.npmjs.com/package/pdfnative-mcp)\n[![Node version](https://img.shields.io/node/v/pdfnative-mcp.svg?logo=node.js)](https://nodejs.org)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![CI](https://github.com/Nizoka/pdfnative-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Nizoka/pdfnative-mcp/actions/workflows/ci.yml)\n[![MCP](https://img.shields.io/badge/MCP-2026--07--28-6f42c1.svg)](https://modelcontextprotocol.io)\n[![pdfnative](https://img.shields.io/badge/pdfnative-1.7-0a7e8c.svg)](https://github.com/Nizoka/pdfnative)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Nizoka/pdfnative-mcp/badge)](https://scorecard.dev/viewer/?uri=github.com/Nizoka/pdfnative-mcp)\n[![CodeQL](https://github.com/Nizoka/pdfnative-mcp/actions/workflows/codeql.yml/badge.svg)](https://github.com/Nizoka/pdfnative-mcp/actions/workflows/codeql.yml)\n\n---\n\n## ✨ Features\n\n`pdfnative-mcp` exposes **28 production-grade tools** to any MCP host:\n\n| Tool                               | Purpose                                                                                          |\n| ---------------------------------- | ------------------------------------------------------------------------------------------------ |\n| `generate_basic_pdf`               | Multi-page documents from **13 block kinds** — `heading`, `paragraph`, `list`, `table`, `image` (JPEG/PNG), `link`, `toc` (printed table of contents), `barcode`, `svg`, `formField`, `chart`, `pageBreak`, `spacer` — every `DocumentBlock` the engine offers. Embedded newlines auto-split into paragraphs. Optional `pdfA`, `print`, `metadata`, `embedFonts`, `watermark`, `outline`, layout options (`pageSize`, `margins`, `headerTemplate` / `footerTemplate`, `compress`, `debug`, `encrypt`). |\n| `inspect_layout` *(new in v1.6.0)* | Read-only **pagination dry run** of the same `blocks` (+ `title`, `footerText`, `pdfA`, `normalize`, `embedFonts`, `pageSize`, `margins`, `headerTemplate`, `footerTemplate`): page count and where every block lands, no PDF produced. |\n| `add_barcode`                      | QR Code, Code 128, EAN-13, Data Matrix, PDF417 — embedded in a single-page PDF.                 |\n| `add_international_text`           | 24 scripts (incl. **Latin** & COLRv1 **colour emoji** with flag / ZWJ sequences) with BiDi & OpenType shaping; multi-lang per document. |\n| `add_table`                        | Tabular reports with smart fields (wrap, repeatHeader, zebra, caption, minRowHeight, cellPadding). |\n| `add_form`                         | Create a **new** interactive AcroForm PDF with text fields, text areas, checkboxes, radio buttons, dropdowns, list boxes (+ `placeholder` hint text). |\n| `read_form_fields`                 | Read-only enumeration of an **existing** AcroForm's field tree (names, types, values, widgets).  |\n| `fill_form`                        | Fill and/or flatten an **existing** AcroForm (non-destructive incremental update).              |\n| `add_chart`                        | Native vector charts v2 — bar / barH / stackedBar / stackedBarH / line / area / scatter / pie / donut, secondary axis, log & time scales, data labels (pure PDF path operators, PDF/A-safe). |\n| `embed_image`                      | Embed a JPEG or PNG image (base64) into a titled PDF document (`align`, `alt` text for tagged output). |\n| `prepare_signature_placeholder`    | Optional step 1 of the sign workflow — create a PDF with a `/Sig` placeholder (signer metadata, `subFilter`, `reserveTimestamp` baked in). |\n| `sign_pdf`                         | PAdES B-B / B-T CMS signature (RSA-SHA256/384/512, ECDSA-SHA256 P-256; `profile: 'pades'`, `timestamp`, `certChainDerBase64`, multiple signatures, pinnable `signingTime`). Auto-injects a placeholder when needed. |\n| `add_ltv` *(new in v1.6.0)*        | PAdES B-LT — embed a `/DSS` with certificates + OCSP/CRL material (operator-configured provider, or caller-supplied offline material). |\n| `timestamp_pdf` *(new in v1.6.0)*  | PAdES B-LTA — append an RFC 3161 `/DocTimeStamp` from the operator-configured TSA; re-run to extend the archival chain. |\n| `verify_pdf`                       | Verify every PAdES signature and document timestamp (integrity + signature value + optional chain trust; a `/DocTimeStamp` counts in `allValid` like any signature); `ltv: true` reports the B-B…B-LTA level. |\n| `validate_pdf`                     | Validate a Tagged PDF for PDF/UA (ISO 14289-1) structural conformance (read-only).              |\n| `add_attachment`                   | Generate a PDF/A-3 document with embedded files (Factur-X / ZUGFeRD invoices).                  |\n| `extract_attachments`              | Read-only extraction of embedded files (Factur-X / ZUGFeRD XML round-trip) with byte-for-byte payloads. |\n| `extract_text`                     | Unicode text extraction (resolves `/ToUnicode`) with optional positioned runs; opens encrypted PDFs via `password`. |\n| `inspect_pdf`                      | Read-only inspection: PDF version, page count, encryption (+ precise `encryptionInfo`), PDF/A claim, signatures (+ inventory, `/DSS`, document timestamps), page boxes, `/Trapped`, attachments, placeholder state, `annotations: true` inventory of existing page annotations. |\n| `update_metadata` *(new in v1.6.0)* | Rewrite `/Info` title / author / subject / keywords (+ XMP, dates included) of an **existing** PDF as an incremental update; pin `modDate` for bytes that are identical on the same host time zone. |\n| `encrypt_pdf`                      | Re-secure a PDF with AES-128 / AES-256 (owner/user passwords, permissions, password rotation).  |\n| `decrypt_pdf`                      | Emit an unencrypted copy of an RC4 / AES-128 / AES-256 document.                                |\n| `merge_pdfs`                       | Concatenate 2–50 PDFs into one via pdfnative's page-tree API (page boxes preserved).            |\n| `split_pdf`                        | Split one PDF into one document per page range (multi-output).                                  |\n| `extract_pages`                    | Pull an arbitrary page subset into a single PDF.                                               |\n| `annotate_pdf`                     | Add markup annotations (highlight, note, square/circle, line, freetext) as a visual overlay — **not** a redaction. |\n| `draft_governance_issue`           | Draft a governance-compliant GitHub issue locally for **human** review; never submits, no network. |\n\n**New in v1.6.0:**\n\n- 🧱 **Full engine coverage — 13 block kinds** — `generate_basic_pdf` accepts every `DocumentBlock` pdfnative offers: the new `table`, `image`, `link`, `toc`, `barcode`, `svg` and `formField` blocks share their body with the dedicated tools (`add_table`, `embed_image`, `add_barcode`, `add_form`) so a standalone artefact and an inline block validate and render identically. Rules: `link` accepts `http:` / `https:` / `mailto:` only (control characters rejected); `image` blocks are bounded (12 M base64 characters each, 24 MiB decoded per call; PNG must be 8-bit, non-interlaced, without alpha or palette — rejected with a remedy); `svg` covers paths, basic shapes and `<text>` (no `transform`, `<g>`, gradients or CSS — silently ignored; nothing is ever fetched); `toc` pairs with `outline: 'auto'`; `formField` under a PDF/A claim reports `PDFA_UNEMBEDDED_FORM_FONT`; `barcode` has no `alt` (engine limitation).\n- 📐 **Layout options on the nine document tools** — `pageSize` (`A4` default, `Letter`, `Legal`, `A3`, `Tabloid`), `margins` (all four, 0–200 pt), `headerTemplate` / `footerTemplate` with `{page}` `{pages}` `{title}` `{date}` (a `footerTemplate` replaces the default footer, so `footerText` is then ignored; `{date}` is the build-day wall clock, not `creationDate`), `compress` (FlateDecode streams — smaller file, different bytes; XMP stays plain under PDF/A) and `debug` (guide rectangles, unmarked content — not for PDF/UA). Absent by default, so default output stays byte-identical.\n- 🔐 **Encryption at build time** — `encrypt` on seven document tools (`generate_basic_pdf`, `add_table`, `add_form`, `add_international_text`, `embed_image`, `add_barcode`, `add_chart`): Standard Security Handler, AES-128 default / AES-256, **keeps the AcroForm** (unlike `encrypt_pdf`, which rebuilds the page tree). Exclusive with `pdfA` (`VALIDATION_ERROR`), never cached; not offered on `prepare_signature_placeholder` (must stay signable) or `add_attachment` (PDF/A-3).\n- 📏 **`inspect_layout`** — the 28th tool: a read-only pagination dry run over the same `blocks` and layout inputs, reporting `totalPages` and each block's page / x / top / width / height without rendering a PDF. Known engine gap: a `toc` block is measured as 0 pt, so documents with a printed contents may paginate one page later than previewed.\n- 🔎 **`inspect_pdf annotations: true`** — lists every page annotation (subtype, 0-based page, rect, contents truncated to 200 chars, title, colour, quadPoints, link URL) plus `annotationCount`; new `check: 'annotations'`.\n- 🖼️ **Image watermarks** — `watermark.image` (JPEG/PNG, default opacity 0.10, own 8 MiB cap) on `generate_basic_pdf` and `add_table`, alone or combined with `text` (default opacity 0.15); `position: 'background' | 'foreground'` for both. Either opacity below 1.0 is rejected under `pdfa1b`.\n- 🧯 **`PDFNATIVE_MCP_MAX_INFLATE_BYTES`** — operator override of the engine's 100 MiB per-stream decompression cap (integer ≥ 1024; an invalid value refuses to start). A capped attachment stream fails `extract_attachments includeData: true` with `PDF_PARSE_FAILED`; `extract_text` degrades to empty page text (the engine swallows per-page decode failures).\n- 📝 **Forms** — `add_form` and `formField` blocks gain `listbox` and `placeholder`; `fieldType: 'textarea'` now reaches the engine as `multilineText` (it was passed through unmapped before and rendered as a single-line field — a bug fix that changes bytes for that input). `embed_image` gains `align` and `alt`.\n- 🔏 **PAdES long-term validation ladder** — `sign_pdf` gains `profile: 'pades'` (ETSI EN 319 142-1 baseline, ESS signing-certificate-v2, `ETSI.CAdES.detached`), `timestamp: true` (B-T, RFC 3161), RSA-SHA384/512, `certChainDerBase64`, `fieldName` / `allowMultiple` for several signatures; new `add_ltv` embeds a `/DSS` (B-LT, `mode: 'online'` through the operator provider or `mode: 'offline'` with caller-supplied DER material); new `timestamp_pdf` appends a `/DocTimeStamp` (B-LTA). `verify_pdf ltv: true` reports profile, timestamp, revocation status and `ltvLevel`. See [`docs/guides/LTV.md`](docs/guides/LTV.md).\n- 🌐 **Network charter** — no outbound request by default. The only egress the server can ever perform goes to the RFC 3161 / OCSP / CRL endpoints the operator configured (`PDFNATIVE_MCP_TSA_URL`, `PDFNATIVE_MCP_REVOCATION`, `PDFNATIVE_MCP_NETWORK_ALLOWED_HOSTS`), behind an SSRF guard; tool arguments can never supply a URL.\n- 🖨️ **Print production** — every document tool accepts `print` (TrimBox / BleedBox / ArtBox / CropBox or the `bleed` shorthand, crop + registration `marks`, `/UserUnit`), `metadata` (`/Author`, `/Subject`, `/Keywords`, `/Trapped`) and `outputIntent` (custom RGB ICC for PDF/A); `viewerPreferences` gains `duplex`, `pickTrayByPDFSize`, `printPageRange`, `numCopies`. `inspect_pdf pages: true` reports the boxes; merge / split / extract preserve them. See [`docs/guides/PRINT.md`](docs/guides/PRINT.md).\n- ✍️ **`update_metadata`** — rewrite `/Info` + XMP of an existing PDF as an incremental update (earlier revisions and signatures preserved verbatim).\n- 📊 **Charts v2** — `stackedBar` / `stackedBarH` / `area` / `scatter`, secondary right axis (`axis2`), `axis.scale: 'log'`, `xAxis.type: 'linear' | 'time'`, `dataLabels`, `labelStride` / `labelRotation`; overlapping category labels are thinned automatically.\n- 📜 **Honest PDF/A** — `embedFonts: true` embeds Noto Sans Latin (base-14 Helvetica is not embedded, so a PDF/A claim on plain Latin text is rejected by veraPDF), `strict: true` fails instead of producing a non-conformant file, `includeDiagnostics: true` echoes engine diagnostics. Local veraPDF script (`npm run validate:pdfa`) over a 26-file corpus (24 validated, 3 of them negative canaries; 2 page-tree outputs skipped) and a fail-closed `VERAPDF_REQUIRED=1` mode; the CI workflow pins the installer by SHA-256 and stays non-blocking in 1.6.0. Known engine gaps: `add_form` output fails PDF/A-2b even with `embedFonts` (unembedded `/DR /Helv`), and a `prepare_signature_placeholder` output is conformant only once signed.\n- 🧰 **`inspect_pdf`** — `signatures: true` inventory, `dss` / `docTimestampCount` / `trapped` (presence-gated), new `check` values `dss`, `docTimestamp`, `trapped`; `checks` lists only the keys you requested, and `signed` is structural (a signed field exists — validity is `verify_pdf`'s job).\n- 🔁 **Reproducible output** — opt-in `creationDate` on all nine document tools pins `/CreationDate`, the XMP dates and the trailer `/ID`; `signingTime` on `prepare_signature_placeholder` (and on `sign_pdf`, now with time-zone offsets) pins `/Sig /M`. Identical bytes on the same host time zone. Backed by the `reproducible_output` prompt.\n- 🛡️ **Hardened boundary** — strict input schemas (unknown or misspelt keys → `VALIDATION_ERROR` instead of being silently ignored); `data:…;base64,` prefixes tolerated, PEM-where-DER and double-encoded payloads rejected with the exact remedy; page-index mistakes on the page-tree tools are `VALIDATION_ERROR` with a 0-based hint; an unknown tool name is a JSON-RPC protocol error (`-32602`, `[UNKNOWN_TOOL]`).\n- 🔑 **HTTP bearer token** — opt-in `PDFNATIVE_MCP_HTTP_TOKEN` gates the Streamable HTTP endpoint (`401` + `WWW-Authenticate` otherwise). Without it the loopback endpoint has no authentication — see [`SECURITY.md`](SECURITY.md).\n- 🧾 **Catalogue** — `tools/list` is ≈ 245 kB (1.5.0: ≈ 108 kB) because every block kind, layout option and `encrypt` fragment is now advertised inline — no `$ref` / `$defs` by policy, so hosts that forward `inputSchema` to function-calling APIs never meet a reference; the server instructions are ≈ 6.7 kB (from 12.9 kB). Structure is guarded by `scripts/tool-shape.mjs` + `tests/catalogue-parity.test.ts`, and `tests/catalogue-superset.test.ts` proves the live catalogue is a superset of the published 1.5.0 one; at most two executable `_meta.examples` per tool, the rest under [`examples/`](examples/). Four new recipe prompts: `pades_ladder`, `print_ready`, `reproducible_output`, `pdfa_valid`.\n- 🐛 **Fixes** — signer metadata (`signerName` / `reason` / `location` / `contactInfo`) never reached the `/Sig` dictionary on pdfnative < 1.7; it is now baked at placeholder time. `verify_pdf` no longer reports `allValid: false` on B-LTA documents (a `/DocTimeStamp` was parsed as a CMS signature).\n- 🔌 **MCP 2026-07-28** on the MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) with automatic fallback to the 2025-era `initialize` handshake — existing hosts keep working unchanged. See [MCP protocol compliance](#-mcp-protocol-compliance).\n- ⬆ **Engine upgrade** — [pdfnative **v1.7.0**](https://github.com/Nizoka/pdfnative) (LTV, print production, charts v2, digest agility, flag / ZWJ emoji sequences, UAX #9 fixes).\n\n**New in v1.5.0:**\n\n- 📊 **Native vector charts** — `add_chart` renders bar / horizontal-bar / line / pie / donut charts as pure PDF path operators (zero rasterisation, PDF/A-safe with auto alt text). `generate_basic_pdf` also accepts a `chart` block for composition with text and tables.\n- 📝 **Fill & flatten forms** — `read_form_fields` lists an existing AcroForm's fields; `fill_form` fills and/or flattens it via a non-destructive incremental update (the counterpart to `add_form`).\n- 🔐 **Encryption round-trip** — `encrypt_pdf` re-secures with AES-128 / AES-256 (RC4 never emitted), `decrypt_pdf` recovers an unencrypted copy, a `password` input opens encrypted sources on the read-only tools, and `merge_pdfs` / `split_pdf` / `extract_pages` gain `password` + `encrypt`.\n- 🔤 **Real text extraction** — `extract_text` now resolves each font's `/ToUnicode` CMap (no more glyph-index output) and can return positioned `runs`.\n- 🔗 **Native MCP resources** — sandboxed generated PDFs become `pdfnative://output/…` resources (`resources/list` + `resources/read`), with a `resource_link` in file-mode results for cross-call re-reference.\n- 🏷️ **Tool annotations** — every tool advertises `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`.\n- ⬆ **Engine upgrade** — [pdfnative **v1.6.0**](https://github.com/Nizoka/pdfnative) (decrypt/re-encrypt, `extractText`, fill/flatten, charts; colour-emoji subset 221 → 1167 glyphs).\n\n**New in v1.4.0:**\n\n- 🤝 **AI governance + human-in-the-loop** — `draft_governance_issue` lets an agent draft a fully compliant GitHub issue **locally** (draft `.md` + machine-readable compliance report). The agent is a *draftsman, never an autonomous submitter*: a human is the only gate, and the server makes **zero** GitHub writes (and, since v1.6.0, no outbound call other than to operator-configured TSA / OCSP / CRL endpoints). Backed by the `governance_contract` and `draft_issue_workflow` MCP prompts.\n- ✏️ **Markup annotations** — `annotate_pdf` overlays highlight, sticky-note, underline, strikeout, squiggly, square, circle, line, and freetext annotations on an existing PDF via incremental update. It is a *visual review layer, not a redaction* — underlying bytes remain.\n- 🔢 **Page labels in `inspect_pdf`** — read-only surfacing of `/PageLabels` ranges (roman, decimal, prefixed).\n- ∑ **Math / scientific script** — `add_international_text` accepts `lang: 'math'` (explicit, like `emoji`) to embed the Noto Sans Math face on demand.\n- 🧩 **MCP prompts** — the server now advertises the `prompts` capability with `governance_contract` and `draft_issue_workflow`.\n- ⬆ **Engine upgrade** — pdfnative **v1.5.0**.\n\n**New in v1.3.0:**\n\n- 🆕 **Three page-tree tools** — `merge_pdfs`, `split_pdf`, `extract_pages` (built on [pdfnative v1.4.0](https://github.com/Nizoka/pdfnative)'s page-tree API; encrypted sources were rejected until v1.5.0 added `password`).\n- 🔖 **Bookmarks, page labels & nested lists** — `generate_basic_pdf` gains `outline` (`'auto'` or explicit tree), `pageLabels`, multi-level `list` items, and `viewerPreferences`.\n- 📐 **Table cell borders & alignment** — `add_table` gains `cellBorders`, `cellVAlign`, and `viewerPreferences`; `add_international_text` gains `viewerPreferences`.\n- 🔐 **Constant-time signing** — `sign_pdf` signs RSA and EC-DER keys through a `node:crypto` provider with a transparent pure-JS fallback (raw P-256 scalars stay pure JS, and verification is pure JS); signatures stay interoperable.\n- ⬆ **Engine upgrade** — pdfnative **v1.4.0**.\n\n- 🆕 **Tool `extract_attachments`** — read embedded files back out of a PDF (completes the Factur-X / ZUGFeRD round-trip) with byte-for-byte payloads, a `filename` filter, and an `includeData: false` metadata-only probe.\n- 💧 **Watermarks** — `generate_basic_pdf` and `add_table` accept an optional `watermark` (text, opacity, angle, colour, position; `image` since v1.6.0) rendered on every page.\n- 🌐 **Unicode `normalize`** — opt-in `NFC`/`NFD`/`NFKC`/`NFKD` on `generate_basic_pdf` and `add_international_text`.\n- 🪙 **Token-frugal reads** — the read-only tools (`inspect_pdf`, `verify_pdf`, `validate_pdf`, `extract_text`, `extract_attachments`; `read_form_fields` since v1.5.0) accept optional `verbosity: 'summary'` and `fields: […]` inputs for ~90% smaller responses on large results, with no loss of the fields agents branch on. Defaults are unchanged.\n- 🪙 **No base64 duplication** — generated PDFs (base64 mode) are returned **once** as an embedded `resource` content block instead of also being copied into `structuredContent`.\n- 🔧 **MCP registry publish fix** — `mcpName` now uses the canonical GitHub login casing (`io.github.Nizoka/pdfnative-mcp`) so the registry's case-sensitive validation accepts the npm package.\n- ⬆ **Dependency** — upgraded to **zod 4**.\n\n**New in v1.1.0:**\n\n- 🆕 **Tool `validate_pdf`** — read-only PDF/UA (ISO 14289-1) structural conformance check.\n- 🆕 **Six new scripts** — Telugu, Sinhala, Tibetan, Khmer, Myanmar, Ethiopic (**24 scripts** total).\n- 🆕 **COLRv1 colour emoji** — native colour emoji with monochrome fallback.\n- 🆕 **Newline sanitizer** — embedded `\\n` in paragraphs auto-splits into separate paragraphs (Safe PDF/A).\n- 🆕 **Automatic NFC normalisation** for `add_international_text`.\n- 🛠 **Engine upgrade** — [pdfnative v1.3.0](https://github.com/Nizoka/pdfnative): the Euro sign / CP-1252 symbols now extract correctly, and wrapped table cells get unique per-line MCIDs (PDF/UA-safe).\n\n**New in v1.0.0:**\n\n- 🆕 **Three new tools:** `verify_pdf`, `add_attachment` (Factur-X / ZUGFeRD), `extract_text`.\n- 🆕 **Smart-table fields:** `wrap`, `repeatHeader`, `zebra`, `caption`, `minRowHeight`, `cellPadding`.\n- 🆕 **`inspect_pdf`** now reports `hasSignaturePlaceholder` and per-attachment summary; new `check` values `'placeholder'` and `'attachments'`.\n- 🆕 **Signing ergonomics:** `sign_pdf` accepts ECDSA SEC1 / PKCS#8 DER keys and auto-injects a `/Sig` placeholder when missing (one-call signing of any PDF).\n- 🆕 **Opt-in cache** (`PDFNATIVE_MCP_CACHE_DIR`): SHA-256 keyed, 1 h TTL, 256 MiB LRU.\n- 🆕 **`_meta.apiVersion`** and per-tool **`_meta.examples`** for AI-agent discovery — see [`docs/API_STABILITY.md`](docs/API_STABILITY.md).\n- 🆕 **AI agent guide:** [`docs/AI_GUIDE.md`](docs/AI_GUIDE.md) — decision tree + common pitfalls. See also the root [`AGENTS.md`](AGENTS.md) operations manual.\n- 🆕 **PDF/A authoring guide:** [`docs/guides/PDFA.md`](docs/guides/PDFA.md).\n- 🛠 **Env-var rename:** `PDFNATIVE_MCP_OUTPUT_DIR` (was `PDFNATIVE_MPC_OUTPUT_DIR`; old name still works with a one-shot deprecation warning).\n- ✅ **Now shipped:** `merge_pdfs`, `split_pdf`, `extract_pages` (v1.3.0), `annotate_pdf` (v1.4.0), the `add_chart` / `read_form_fields` / `fill_form` / `encrypt_pdf` / `decrypt_pdf` tools plus the encrypted round-trip and native MCP resources (v1.5.0), and `add_ltv` / `timestamp_pdf` / `update_metadata` plus print production and charts v2 (v1.6.0). `redact_pdf` stays **deferred** — pdfnative can overlay/flatten but not *remove* page content, and an overlay-only \"redaction\" would create false security, so it is intentionally not shipped (tracked as an upstream content-removal request).\n\nAll tools support two output modes:\n\n- **`base64`** *(default)* — the generated PDF is returned **once** as an embedded `resource` content block (a `data:application/pdf;base64,…` URI); `structuredContent` carries only `{ mode, sizeBytes }` (plus `diagnostics[]` when `includeDiagnostics: true`, and a `summary` for `add_ltv`).\n- **`file`** — the PDF is written to a sandboxed directory configured via `PDFNATIVE_MCP_OUTPUT_DIR`. File output is disabled unless this variable is set; absolute paths, path traversal, non-`.pdf` extensions, and NUL bytes are all rejected.\n\n> **Upgrading from v1.1.0:** the only behaviour change is that base64-mode bytes are\n> no longer duplicated into `structuredContent.base64`. Read them from the embedded\n> `resource` block instead:\n>\n> ```diff\n> - const base64 = response.structuredContent.base64;   // v1.1.0\n> + const block = response.content.find((c) => c.type === 'resource');\n> + const base64 = block.resource.blob;                  // v1.2.0\n> ```\n\n\n**Token-frugal reads (v1.2.0).** The seven read-only tools (`inspect_pdf`, `verify_pdf`, `validate_pdf`, `extract_text`, `extract_attachments`, `read_form_fields`, `inspect_layout`) accept two optional inputs:\n\n- `verbosity: 'summary'` — returns a compact scalar-only verdict (drops the heavy arrays / full text). E.g. `verify_pdf` → `{ signatureCount, allValid, invalid, summary }` (+ `ltvLevel` with `ltv: true`); `inspect_pdf` keeps `docTimestampCount` / `trapped` / `checksPassed` when present.\n- `fields: ['a', 'b.c']` — projects the structured result to named dot-paths; composes after `verbosity`. Unmatched paths are omitted and reported in `_meta.unmatchedFields` (with `_meta.availableFields`).\n\nSmallest “is this PDF signed and valid?” probe: `{ \"pdfBase64\": \"…\", \"verbosity\": \"summary\", \"fields\": [\"allValid\"] }`.\n\n### Why pdfnative?\n\n`pdfnative-mcp` inherits every guarantee of the underlying engine:\n\n- **Zero runtime dependencies in the engine** — pure JavaScript, no native bindings (this server adds only the MCP SDK and zod: three runtime dependencies in total).\n- **ISO 32000-1 (PDF 1.7)** compliant output.\n- **PDF/A-1b/2b/2u/3b**, **AES-128/256 encryption**, **AcroForm**, **digital signatures**.\n- **24 scripts** (25 `lang` codes incl. `emoji` and `math`) with built-in BiDi reordering, Arabic positional shaping, Thai/Devanagari/Bengali/Tamil OpenType shaping.\n- Tree-shakeable ESM build.\n\n---\n\n## 🚀 Installation\n\n```bash\n# Run directly with npx (recommended for MCP clients)\nnpx -y pdfnative-mcp\n\n# Or install globally\nnpm install -g pdfnative-mcp\npdfnative-mcp\n```\n\nRequirements: **Node.js ≥ 22**.\n\n---\n\n## ⚙️ Configuration\n\n### Claude Desktop\n\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"pdfnative\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"pdfnative-mcp\"],\n      \"env\": {\n        \"PDFNATIVE_MCP_OUTPUT_DIR\": \"/Users/you/Documents/mcp-pdfs\"\n      }\n    }\n  }\n}\n```\n\n### Cursor / Continue / Zed / Windsurf / Cline / Roo Code\n\nAny MCP-compatible client that supports stdio servers will work. Use the same `command` + `args` + `env` triple. Example for **Cursor** (`~/.cursor/mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"pdfnative\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"pdfnative-mcp\"],\n      \"env\": { \"PDFNATIVE_MCP_OUTPUT_DIR\": \"/Users/you/Documents/mcp-pdfs\" }\n    }\n  }\n}\n```\n\n**Windsurf / Cline / Roo Code** use the same shape inside their respective MCP config files.\n\n### 🌐 Supported AI Ecosystem & Clients\n\n`pdfnative-mcp` is designed for MCP-native environments and works with clients that support MCP over stdio or Streamable HTTP.\n\nCommunity-verified compatibility includes:\n\n- **[Ontheia](https://ontheia.ai)** — a self-hosted, open-source AI agent platform (privacy-first). Reported as working out of the box in [issue #41](https://github.com/Nizoka/pdfnative-mcp/issues/41) and listed on Ontheia's [compatible MCP servers page](https://docs.ontheia.ai/en/getting-started/03_compatible-mcp-servers/).\n\n### 🔌 MCP protocol compliance\n\nSince v1.6.0 the server is built on the MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) and speaks **MCP 2026-07-28**:\n\n- **Stateless serving** — `server/discover` replaces the session handshake; every result carries `resultType` and the `_meta` `serverInfo` envelope. Over HTTP, 2026-07-28 clients send `Mcp-Method` / `Mcp-Name` headers with each `POST /mcp`.\n- **Cache hints** — `tools/list` and `prompts/list` are `public` with a 24 h `ttlMs`, `server/discover` is `public` for 1 h, and `resources/list` / `resources/templates/list` / `resources/read` are `private` with `ttlMs: 0` (generated PDFs are per-host user data).\n- **Resource errors** — an unknown resource URI is reported as JSON-RPC `-32602` (Invalid params), as the 2026-07-28 specification requires.\n- **Automatic legacy fallback** — a client that opens with `initialize` (2025-11-25, 2025-06-18 or 2025-03-26) is served through the SDK's legacy path on both stdio and HTTP. Nothing changes for existing hosts.\n- **HTTP** — `GET` / `DELETE /mcp` answer **405** (no SSE resumability; the server is stateless). The loopback bind and the `Host` / `Origin` guard are unchanged, and the `Origin` port must now equal the server port (the SDK check alone is port-agnostic); `PDFNATIVE_MCP_HTTP_TOKEN` adds an opt-in bearer-token gate (`401` + `WWW-Authenticate` without it). JSON-RPC batch arrays (2025-03-26) are accepted over HTTP. Keep-alive connections no longer accumulate socket listeners.\n- **stdio** — as in every SDK release to date, a request sent before `initialize` is dropped without a reply and JSON-RPC batch arrays are not accepted on stdio (unchanged from 1.5.0; no major host batches).\n- **Protocol errors** — `tools/call` with an unknown tool name is a JSON-RPC error (`-32602`, `[UNKNOWN_TOOL] Unknown tool: …`) rather than an `isError` result, as the specification classifies it; `isError: true` is reserved for execution failures.\n- **Output schemas** — every `structuredContent` validates against the tool's `outputSchema` (a 2026-07-28 MUST), including `verbosity: 'summary'` and `fields` projections: the seven read tools declare projectable schemas (all properties optional, `additionalProperties: false` kept). Input schemas carry no `$schema` keyword by policy (MCP ≥ 2025-11-25 defaults to JSON Schema 2020-12; some hosts forward `inputSchema` to function-calling APIs that reject unknown keywords). `serverInfo` carries `websiteUrl`; the resource template is `pdfnative://output/{+path}`.\n\nThe `tools/call` payload (`content`, `structuredContent`, `isError`) is identical between the 2026-07-28 path and the legacy path; `tests/http-modern.test.ts` asserts it, and `tests/schema-conformance.test.ts` validates `structuredContent` with the SDK's JSON Schema 2020-12 validator.\n\n| Client                                                   | Transport        | Protocol negotiated                                 |\n| -------------------------------------------------------- | ---------------- | --------------------------------------------------- |\n| Claude Desktop, Cursor, Continue, Zed, Windsurf, Cline   | stdio            | legacy `initialize` (2025-xx) — unchanged           |\n| ChatGPT and other Streamable HTTP hosts                  | HTTP `POST /mcp` | legacy stateless streamable HTTP — unchanged        |\n| MCP 2026-07-28 clients (SDK v2 `Client`, current MCP Inspector) | stdio / HTTP | `server/discover`, cache hints, `_meta` envelope |\n| Ontheia                                                  | stdio            | legacy `initialize` (community-verified, #41)       |\n\n### Environment variables\n\n| Variable                      | Purpose                                                                            |\n| ----------------------------- | ---------------------------------------------------------------------------------- |\n| `PDFNATIVE_MCP_OUTPUT_DIR`    | Absolute path to the sandbox directory. **Required to enable `outputMode: 'file'`.** |\n| `PDFNATIVE_MCP_CACHE_DIR`     | Absolute path to enable the persistent SHA-256-keyed result cache (1 h TTL, 256 MiB LRU; key namespaced by tool API + package version). When unset, the cache is disabled. Never caches `encrypt_pdf` / `decrypt_pdf` / `sign_pdf` / `add_ltv` / `timestamp_pdf` / `update_metadata` or file-mode calls; a hit carries `_meta.cached: true` and returns the earlier call's bytes. |\n| `PDFNATIVE_MCP_PORT`          | When set to a valid port (1–65535), starts an HTTP server on `http://127.0.0.1:<port>/mcp` instead of stdio. Binds loopback only and enables DNS-rebinding protection (foreign `Host`/`Origin` → **403**). **No authentication unless `PDFNATIVE_MCP_HTTP_TOKEN` is set** — other local processes can reach the endpoint. |\n| `PDFNATIVE_MCP_HTTP_TOKEN`    | *(v1.6.0, secret)* Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every `/mcp` request must carry `Authorization: Bearer <token>`; otherwise **401** + `WWW-Authenticate: Bearer realm=\"pdfnative-mcp\"` (with `error=\"invalid_token\"` only when credentials were sent — RFC 6750 §3.1). Compared constant-time, never logged. |\n| `PDFNATIVE_MCP_MAX_INFLATE_BYTES` | *(v1.6.0)* Overrides the engine's 100 MiB per-stream decompression cap (zip-bomb guard): a positive integer number of bytes ≥ 1024, read once at startup — an invalid value refuses to start. Lower it on a shared host, raise it for trusted archives of large scans. A capped attachment stream fails `extract_attachments includeData: true` with `PDF_PARSE_FAILED`; `extract_text` degrades to empty page text for a capped content stream (engine behaviour, no error surfaced). |\n| `PDFNATIVE_MCP_TSA_URL`       | *(v1.6.0)* Absolute `http(s)` URL of the RFC 3161 timestamp authority used by `sign_pdf timestamp: true` and `timestamp_pdf`. Unset: `TSA_NOT_CONFIGURED`, no request is made. |\n| `PDFNATIVE_MCP_TSA_AUTH`      | *(v1.6.0, secret)* Optional `Authorization` header value sent to the TSA. Never logged or echoed. |\n| `PDFNATIVE_MCP_REVOCATION`    | *(v1.6.0)* `ocsp`, `crl` or `ocsp,crl` — enables online revocation collection for `add_ltv mode: 'online'`. Unset: `REVOCATION_NOT_CONFIGURED`. |\n| `PDFNATIVE_MCP_NETWORK_ALLOWED_HOSTS` | *(v1.6.0)* Comma-separated allow-list (`host`, `host:port`, `*.suffix`) for OCSP / CRL responders. **Mandatory** when `PDFNATIVE_MCP_REVOCATION` is set — responder URLs come from untrusted certificates. |\n| `PDFNATIVE_MCP_NETWORK_TIMEOUT_MS` | *(v1.6.0)* Per-request timeout for TSA / OCSP / CRL calls, 1000–120000 ms (default 10000). |\n\n---\n\n## 🛠 Tool reference\n\n### `generate_basic_pdf`\n\n```jsonc\n{\n  \"title\": \"Q1 2026 Report\",\n  \"blocks\": [\n    { \"type\": \"heading\", \"text\": \"Executive summary\", \"level\": 1 },\n    { \"type\": \"paragraph\", \"text\": \"Revenue grew 24% year over year.\" },\n    { \"type\": \"list\", \"style\": \"bullet\", \"items\": [\"Strong APAC\", \"Stable EU\", \"Soft NA\"] },\n    { \"type\": \"pageBreak\" },\n    { \"type\": \"heading\", \"text\": \"Details\", \"level\": 2 }\n  ],\n  \"footerText\": \"Confidential — Internal use only\",\n  \"outputMode\": \"base64\"\n}\n```\n\nThe 13 block kinds: `heading`, `paragraph`, `list`, `table`, `image`, `link`, `toc`, `barcode`, `svg`, `formField`, `chart`, `pageBreak`, `spacer`. A composite report:\n\n```jsonc\n{\n  \"title\": \"Quarterly report\",\n  \"blocks\": [\n    { \"type\": \"toc\" },\n    { \"type\": \"heading\", \"text\": \"Sales\", \"level\": 1 },\n    { \"type\": \"table\", \"headers\": [\"Region\", \"Revenue\"], \"rows\": [[\"EMEA\", \"1.2 M\"], [\"APAC\", \"0.9 M\"]], \"zebra\": true },\n    { \"type\": \"image\", \"imageBase64\": \"<base64 JPEG>\", \"mimeType\": \"image/jpeg\", \"width\": 300, \"alt\": \"Revenue chart\" },\n    { \"type\": \"svg\", \"data\": \"M10 10 H 90 V 90 H 10 Z\", \"viewBox\": [0, 0, 100, 100], \"fill\": \"#0a7e8c\" },\n    { \"type\": \"barcode\", \"format\": \"qr\", \"data\": \"https://example.com/q1\", \"align\": \"center\" },\n    { \"type\": \"link\", \"text\": \"Full dataset\", \"url\": \"https://example.com/data\" },\n    { \"type\": \"formField\", \"fieldType\": \"text\", \"name\": \"reviewer\", \"label\": \"Reviewed by\" }\n  ],\n  \"outline\": \"auto\",\n  \"pageSize\": \"Letter\",\n  \"headerTemplate\": { \"right\": \"{title} — page {page}/{pages}\" },\n  \"embedFonts\": true\n}\n```\n\nBlock rules: `table`, `barcode`, `formField` and `chart` take the same body as `add_table` / `add_barcode` / `add_form` / `add_chart`; `link` URLs must be `http:`, `https:` or `mailto:`; `image` blocks are capped at 12 M base64 characters each and 24 MiB decoded per call (PNG: 8-bit greyscale/RGB, non-interlaced, no alpha, no palette — otherwise `VALIDATION_ERROR` with a remedy); `svg` supports `<path>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polyline>`, `<polygon>`, `<text>`/`<tspan>` and silently ignores `transform`, `<g>`, `<use>`, `<image>`, gradients, opacity and CSS (no external reference is ever fetched); `toc` is built from the heading blocks and pairs with `outline: 'auto'`; `formField` under `pdfA` reports `PDFA_UNEMBEDDED_FORM_FONT` (`strict: true` fails); `barcode` has no `alt`. Use `inspect_layout` with the same inputs to preview the pagination before rendering.\n\n### `add_barcode`\n\n```jsonc\n{\n  \"format\": \"qr\",\n  \"data\": \"https://pdfnative.dev\",\n  \"caption\": \"Scan to learn more\",\n  \"ecLevel\": \"H\",\n  \"outputMode\": \"file\",\n  \"outputPath\": \"tickets/event-42.pdf\"\n}\n```\n\nSupported formats: `qr`, `code128`, `ean13`, `datamatrix`, `pdf417`.\n\n### `add_international_text`\n\n```jsonc\n{\n  \"title\": \"مرحبا بالعالم\",\n  \"lang\": \"ar\",\n  \"paragraphs\": [\n    \"هذا اختبار للنص العربي مع تشكيل OpenType ومحارف ثنائية الاتجاه.\",\n    \"Mixed content: العربية + English ✓\"\n  ]\n}\n```\n\nSupported `lang` codes (25): `ar`, `he`, `th`, `ja`, `zh`, `ko`, `el`, `hi`, `bn`, `ta`, `ru`, `ka`, `hy`, `tr`, `pl`, `vi`, `latin`, `te`, `si`, `bo`, `km`, `my`, `am`, `emoji`, `math`. Fonts are always embedded (no `embedFonts` input); pin `creationDate` for byte-identical output.\n\nMulti-script documents — pass an array or comma-separated list:\n\n```jsonc\n{\n  \"title\": \"Mixed Script\",\n  \"lang\": [\"ar\", \"emoji\"],\n  \"paragraphs\": [\"العربية مع رموز 🎉🚀\"],\n  \"pdfA\": \"pdfa2u\"\n}\n```\n\n### `sign_pdf`\n\nAs of v1.0.0, `sign_pdf` auto-injects a `/Sig` placeholder when missing — you can sign **any** PDF in one call:\n\n```jsonc\n{\n  \"pdfBase64\": \"<any base64 PDF>\",\n  \"algorithm\": \"rsa-sha256\",\n  \"certDerBase64\": \"<base64 X.509 cert in DER>\",\n  \"rsaKeyPkcs1DerBase64\": \"<base64 PKCS#1 RSAPrivateKey DER>\",\n  \"signerName\": \"Alice\",\n  \"reason\": \"Approval\",\n  \"location\": \"Paris, FR\",\n  \"signingTime\": \"2026-01-15T10:30:00Z\"\n}\n```\n\nFor ECDSA P-256: use `algorithm: \"ecdsa-sha256\"` and supply either `ecPrivateKeyDerBase64` (SEC1 or PKCS#8 DER) or `ecPrivateScalarHex` (64 hex chars).\n\nPEM → DER conversion:\n\n```bash\nopenssl x509 -in cert.pem -outform DER | base64 -w0                 # cert\nopenssl rsa  -in key.pem  -outform DER -traditional | base64 -w0    # RSA PKCS#1\nopenssl pkey -in key.pem  -outform DER | base64 -w0                 # ECDSA\n```\n\n> Use `prepare_signature_placeholder` only when you need to customize the placeholder (e.g. larger `placeholderBytes` for >4096-bit RSA keys, `subFilter: 'ETSI.CAdES.detached'`, `reserveTimestamp: true`). Otherwise call `sign_pdf` directly.\n\n**PAdES ladder (v1.6.0).** `sign_pdf` with `profile: \"pades\"` produces a B-B signature; add `timestamp: true` for B-T (needs `PDFNATIVE_MCP_TSA_URL`), then `add_ltv` (B-LT) and `timestamp_pdf` (B-LTA):\n\n```jsonc\n// 1. sign_pdf  { ..., \"profile\": \"pades\", \"timestamp\": true, \"certChainDerBase64\": [\"<intermediate DER>\"] }\n// 2. add_ltv   { \"pdfBase64\": \"<signed>\", \"mode\": \"online\" }            // or \"offline\" + certificatesDerBase64 / ocspResponsesDerBase64 / crlsDerBase64\n// 3. timestamp_pdf { \"pdfBase64\": \"<ltv>\" }                              // re-run before the TSA certificate expires\n// 4. verify_pdf { \"pdfBase64\": \"<final>\", \"ltv\": true }                  // -> ltvLevel: \"B-LTA\"\n```\n\nSigner metadata (`signerName`, `reason`, `location`, `contactInfo`) is baked into the placeholder; `fieldName` selects one of several unsigned placeholders (`PLACEHOLDER_AMBIGUOUS` otherwise) and `allowMultiple: true` adds a further signature. See [`docs/guides/LTV.md`](docs/guides/LTV.md).\n\n---\n\n### `add_table`\n\n```jsonc\n{\n  \"title\": \"Monthly Sales\",\n  \"headers\": [\"Region\", \"Units\", \"Revenue\"],\n  \"rows\": [\n    [\"APAC\", \"1200\", \"$240,000\"],\n    [\"EMEA\", \"800\", \"$160,000\"]\n  ],\n  \"infoItems\": [{ \"label\": \"Period\", \"value\": \"January 2025\" }],\n  \"footerText\": \"Internal use only\",\n  \"outputMode\": \"base64\"\n}\n```\n\n### `add_form`\n\n```jsonc\n{\n  \"title\": \"Employee Onboarding\",\n  \"fields\": [\n    { \"fieldType\": \"text\", \"name\": \"fullName\", \"label\": \"Full Name\", \"required\": true },\n    { \"fieldType\": \"dropdown\", \"name\": \"dept\", \"label\": \"Department\", \"options\": [\"Engineering\", \"Sales\", \"HR\"] },\n    { \"fieldType\": \"checkbox\", \"name\": \"agree\", \"label\": \"I agree to the terms\", \"checked\": false },\n    { \"fieldType\": \"listbox\", \"name\": \"skills\", \"label\": \"Skills\", \"options\": [\"TypeScript\", \"PDF\", \"MCP\"] },\n    { \"fieldType\": \"textarea\", \"name\": \"notes\", \"label\": \"Notes\", \"placeholder\": \"Anything we should know?\" }\n  ],\n  \"outputMode\": \"base64\"\n}\n```\n\nField types: `text`, `textarea` (multi-line, `/Ff 4096`), `checkbox`, `radio`, `dropdown`, `listbox`; `placeholder` shows hint text while a field is empty. Add `encrypt` to produce a password-protected form that keeps its AcroForm. Under a PDF/A claim the widget appearance font is not embedded (`PDFA_UNEMBEDDED_FORM_FONT`).\n\n### `embed_image`\n\n```jsonc\n{\n  \"title\": \"Product Photo\",\n  \"imageBase64\": \"<base64-encoded JPEG bytes>\",\n  \"mimeType\": \"image/jpeg\",\n  \"caption\": \"Front view of Model X\",\n  \"width\": 400,\n  \"align\": \"center\",\n  \"alt\": \"Front view of the Model X chassis\",\n  \"outputMode\": \"base64\"\n}\n```\n\n> **Note:** the engine's PNG decoder accepts 8-bit, non-interlaced greyscale / RGB images only. Alpha-channel (colour type 4 / 6), palette (type 3), 16-bit and interlaced PNGs are rejected at the boundary with `VALIDATION_ERROR` and a remedy (flatten or re-export) — the same rule applies to `image` blocks and image watermarks. `embed_image.imageBase64` keeps its 1.5.0 contract with no length bound; the 12 M-character cap applies to inline `image` blocks and watermark images only.\n\n### `prepare_signature_placeholder`\n\n```jsonc\n{\n  \"title\": \"Service Agreement\",\n  \"signerName\": \"Alice Dupont\",\n  \"reason\": \"Approved\",\n  \"location\": \"Paris, FR\",\n  \"blocks\": [\n    { \"type\": \"paragraph\", \"text\": \"By signing below, I accept the terms and conditions.\" }\n  ],\n  \"outputMode\": \"base64\"\n}\n```\n\nPass the returned PDF bytes to `sign_pdf` to complete the signing workflow.\n\n### `inspect_pdf`\n\nRead-only structural and security inspection — useful for downstream verification, CI assertions, and AI agents that need to reason about a PDF before acting on it.\n\n```jsonc\n{\n  \"pdfBase64\": \"<base64 PDF>\",\n  \"pages\": true,\n  \"check\": [\"pdfa\", \"signed\", \"attachments\"]\n}\n```\n\nReturns:\n\n```jsonc\n{\n  \"version\": \"1.7\",\n  \"pageCount\": 3,\n  \"encryption\": \"none\",          // 'none' | 'aes-128' | 'aes-256' | 'rc4' | 'unknown'\n  \"pdfA\": \"3B\",                  // null when no PDF/A claim is present\n  \"signatureCount\": 1,\n  \"hasSignaturePlaceholder\": false,\n  \"attachments\": [{ \"filename\": \"factur-x.xml\", \"mimeType\": \"application/xml\", \"sizeBytes\": 1234, \"relationship\": \"Source\" }],\n  \"info\": { \"Producer\": \"pdfnative\", \"Title\": \"Invoice INV-2025-001\" },\n  \"perPage\": [{ \"index\": 0, \"width\": 595, \"height\": 842 }],\n  \"checks\": { \"pdfa\": true, \"signed\": true, \"attachments\": true },\n  \"checksPassed\": true\n}\n```\n\n`check[]` accepts any of `'pdfa'`, `'signed'`, `'encrypted'`, `'placeholder'`, `'attachments'`, `'dss'`, `'docTimestamp'`, `'trapped'`, `'annotations'` (the last four since v1.6.0). `checksPassed` is the AND of all requested checks. `signatures: true` adds a per-field inventory (`subFilter`, `isDocTimestamp`, `isPlaceholder`, `byteRange`, `vriKey`); `annotations: true` adds `annotations[]` (every `/Annots` entry: 0-based `page`, `subtype`, `rect`, and when present `contents` truncated to 200 chars, `title`, `color`, `quadPoints`, link `url`) plus `annotationCount`; `dss`, `docTimestampCount` and `trapped` appear only when present; with `pages: true` each `perPage` entry also carries `trimBox` / `bleedBox` / `artBox` / `cropBox` / `userUnit` when set.\n\n### `inspect_layout`\n\nRead-only pagination dry run — the same `blocks` as `generate_basic_pdf` plus every input that moves a block (`title`, `footerText`, `pdfA`, `normalize`, `embedFonts`, `pageSize`, `margins`, `headerTemplate`, `footerTemplate`). No PDF is produced; pass exactly what you will give `generate_basic_pdf` and `totalPages` matches.\n\n```jsonc\n{ \"title\": \"Memo\", \"blocks\": [{ \"type\": \"paragraph\", \"text\": \"Short note.\" }], \"pageSize\": \"Letter\", \"verbosity\": \"summary\", \"fields\": [\"totalPages\"] }\n```\n\nThe full result carries `pageWidth`, `pageHeight`, `margins`, `totalPages` and `pages[].blocks[]` (`type`, `page`, `x`, `top`, `width`, `height` in points, rounded to two decimals). Known engine gap: a `toc` block is measured as 0 pt here, so a document with a printed contents may paginate one page later than previewed.\n\n### `validate_pdf`\n\nRead-only **PDF/UA (ISO 14289-1)** structural conformance check for a Tagged PDF. Generate an accessible document with any tool using `pdfA` (e.g. `pdfA: 'pdfa2u'`), then validate the result:\n\n```jsonc\n{ \"pdfBase64\": \"<tagged-pdf-base64>\" }\n```\n\nReturns:\n\n```jsonc\n{\n  \"standard\": \"pdf-ua-1\",\n  \"valid\": true,\n  \"errors\": [],          // blocking structural violations (empty when valid)\n  \"warnings\": [],        // non-blocking best-practice recommendations\n  \"summary\": \"PDF/UA structural prerequisites hold.\"\n}\n```\n\nIt verifies catalog `/MarkInfo /Marked true`, `/StructTreeRoot` (+ `/ParentTree`), `/Metadata` (XMP), `/Lang`, and per-page MCID uniqueness. This is a fast developer-time gate — **not** a substitute for a full reference validator (veraPDF), which additionally checks fonts, colour, and rendering.\n\n### `annotate_pdf`\n\nOverlay markup annotations on an existing PDF via incremental update. This is a **visual review layer, not a redaction** — the underlying content is untouched.\n\n```jsonc\n{\n  \"pdfBase64\": \"<base64 PDF>\",\n  \"annotations\": [\n    { \"type\": \"highlight\", \"page\": 0, \"rect\": [72, 700, 520, 715], \"color\": [1, 1, 0], \"contents\": \"Check this figure\" },\n    { \"type\": \"text\", \"page\": 0, \"rect\": [540, 700, 560, 720], \"contents\": \"Reviewer note\" }\n  ]\n}\n```\n\nTypes: `text`, `highlight`, `underline`, `strikeout`, `squiggly`, `square`, `circle`, `line`, `freetext`. Page indices are 0-based. Encrypted sources are rejected (`ENCRYPTED_SOURCE`) — run `decrypt_pdf` first (drops signatures / AcroForm), annotate, then `encrypt_pdf` again.\n\n### `draft_governance_issue`\n\nDraft a governance-compliant GitHub issue **locally** for a human to review and submit. The server never contacts GitHub (its only possible egress is the operator-configured TSA / OCSP / CRL endpoints — see [Network & egress](#network--egress)); it returns the draft Markdown plus a machine-readable compliance report.\n\n```jsonc\n{\n  \"title\": \"add_table drops the caption on the second page\",\n  \"issueType\": \"bug\",\n  \"summary\": \"The table caption is only rendered on page 1 when repeatHeader is true.\",\n  \"reproduction\": { \"command\": \"add_table with caption + repeatHeader over 2 pages (examples/bordered-table.json, then inspect_pdf)\", \"result\": \"Page 2 has no caption row.\" },\n  \"expectedBehavior\": \"The caption repeats with the header on every page.\",\n  \"duplicateSearchPerformed\": true\n}\n```\n\nA draft that proposes a runtime dependency, omits a reproduction, or sets `duplicateSearchPerformed: false` is rejected with `GOVERNANCE_VIOLATION`. See [`docs/guides/AI_GOVERNANCE.md`](docs/guides/AI_GOVERNANCE.md) for the full human-in-the-loop contract.\n\n### `verify_pdf`, `add_attachment`, `extract_text`\n\nSee the dedicated sections in [`docs/AI_GUIDE.md`](docs/AI_GUIDE.md) and the reference in [`docs/KNOWLEDGE_BASE.md`](docs/KNOWLEDGE_BASE.md). Ready-to-run examples live under [`examples/`](examples/).\n\n---\n\n## 🔐 Security model\n\n`pdfnative-mcp` runs **inside the host process** and exposes a stdio MCP server (or a loopback-only HTTP endpoint). It does **not** perform any I/O outside the configured sandbox.\n\n- **File writes** are gated by `PDFNATIVE_MCP_OUTPUT_DIR`. When unset, the `file` output mode is rejected with a `SecurityError`.\n- **Path resolution** rejects absolute paths, traversal sequences (`..`), NUL bytes, and any extension other than `.pdf`.\n- **Output size** is capped at 50 MB per call.\n- **Inputs** are validated against strict JSON Schemas + Zod runtime checks at the boundary of every tool — unknown or misspelt keys (top-level or nested) are rejected with `VALIDATION_ERROR`, and base64 / DER payloads are sanity-checked (`data:` prefix tolerated, PEM or double-encoded input rejected with the remedy) before any parser runs.\n- **HTTP transport** (`PDFNATIVE_MCP_PORT`) binds loopback only; it has **no authentication** unless `PDFNATIVE_MCP_HTTP_TOKEN` is set (then `401` without a valid bearer token).\n\n### Network & egress\n\nThe server makes **no outbound network call by default**. The only egress it can ever perform goes to the RFC 3161 / OCSP / CRL endpoints the **operator** configured in the environment for PAdES long-term validation (`PDFNATIVE_MCP_TSA_URL`, `PDFNATIVE_MCP_REVOCATION`, `PDFNATIVE_MCP_NETWORK_ALLOWED_HOSTS`) — never to a URL supplied by a tool argument, never to GitHub, never for telemetry. Without that configuration `sign_pdf timestamp: true`, `timestamp_pdf` and `add_ltv mode: 'online'` fail fast with `TSA_NOT_CONFIGURED` / `REVOCATION_NOT_CONFIGURED` before touching the document; `add_ltv mode: 'offline'` embeds caller-supplied material with zero network access.\n\nOCSP / CRL URLs come from the AIA / CRL-distribution-point extensions of untrusted certificates inside the PDF, so every fetch passes an SSRF guard:\n\n- host must match the operator allow-list (`host`, `host:port` or `*.suffix`; bare wildcards are rejected). Entries are **hostnames**, not URLs: a `host:port` entry only matches URLs carrying an *explicit* port (the URL parser drops default `:80` / `:443` — list the bare host for those); wildcard entries cannot carry a port; IDN hostnames must be listed in punycode (`xn--…`); IPv6 literals in brackets (`[2001:db8::1]`);\n- `http:` / `https:` only, no embedded credentials, redirects are never followed;\n- loopback, link-local, private, unique-local, CGNAT, unspecified and multicast address literals (including decimal / octal / hex spellings and IPv4-mapped IPv6) are rejected unless that literal is allow-listed verbatim. The guard checks **literals only** — a listed hostname that resolves to an internal address (DNS rebinding) is not detected, since there is no resolver without adding a dependency; allow-list only hosts you control;\n- per-request timeout (`PDFNATIVE_MCP_NETWORK_TIMEOUT_MS`) and response caps (256 KiB TSA, 1 MiB OCSP, 16 MiB CRL) enforced **while streaming**, so an oversized response is cut off rather than buffered;\n- OCSP responses and CRLs returned by responders are parse-validated before `add_ltv` embeds them;\n- the TSA URL is operator-trusted (scheme + credential checks only); the `PDFNATIVE_MCP_TSA_AUTH` secret is never logged or echoed in error messages.\n\nProviders are built per call and passed through pdfnative's per-call options — the process-wide provider setters are never used, so concurrent requests share nothing. The `server/discover` instructions report the current egress policy (endpoint kinds only, never secrets).\n\nSee [SECURITY.md](SECURITY.md) for the responsible disclosure process and [`docs/guides/LTV.md`](docs/guides/LTV.md) for the operator setup.\n\n---\n\n## 🧪 Local development\n\n```bash\ngit clone https://github.com/Nizoka/pdfnative-mcp.git\ncd pdfnative-mcp\nnpm install\nnpm run typecheck\nnpm run lint\nnpm test\nnpm run build\nnpm run validate:pdfa     # advisory: veraPDF over the 26-file PDF/A corpus (24 validated; skips when veraPDF is absent; VERAPDF_REQUIRED=1 fails closed)\nnode scripts/tool-shape.mjs --write   # only after a deliberate tools/list schema change (catalogue parity fixture)\n```\n\nSmoke-test the server over stdio:\n\n```bash\nnode dist/cli.js\n# In another terminal, send a JSON-RPC initialize request via stdin (e.g. with mcp-inspector).\n```\n\n> **Contributors:** see [docs/guides/LOCAL_TESTING.md](docs/guides/LOCAL_TESTING.md) for the full local-verification workflow — the quality gate, examples-as-tests, validating that generated PDFs are structurally correct (`assertValidPdf`, `inspect_pdf`, `validate_pdf`, `verify_pdf`), opening output in a viewer, external PDF/A checking with veraPDF, and the MCP Inspector.\n\n## 📣 Release process\n\n`pdfnative-mcp` follows the same release formalism as `pdfnative`:\n\n- One release note file per tag in `release-notes/vX.Y.Z.md`\n- `CHANGELOG.md` mirrors each release bullet list\n- GitHub Release body is copied from `release-notes/vX.Y.Z.md`\n- npm publication is handled by GitHub Actions Trusted Publishing (OIDC), without `NPM_TOKEN`\n\nSee `release-notes/TEMPLATE.md` for the canonical structure and publication checklist.\n\n---\n\n## 📚 Project structure\n\n```\nsrc/\n├── cli.ts                      # entrypoint: stdio (default) or Streamable HTTP (PDFNATIVE_MCP_PORT)\n├── http.ts                     # Node http <-> Web Request/Response bridge + Host/Origin loopback guard\n├── auth.ts                     # opt-in HTTP bearer token (PDFNATIVE_MCP_HTTP_TOKEN)\n├── base64.ts                   # base64 / DER boundary decoding with agent-facing diagnostics\n├── index.ts                    # public library exports\n├── server.ts                   # Server factory, tool registry, cache hints, SERVER_INSTRUCTIONS\n├── network.ts                  # operator-configured TSA / OCSP / CRL egress + SSRF guard\n├── print.ts                    # print-production schema (boxes, bleed, marks, userUnit, outputIntent, metadata, creationDate)\n├── diagnostics.ts              # PDF/A diagnostics sink, strict / includeDiagnostics / embedFonts\n├── chart.ts                    # charts v2 schema + ChartBlock mapper\n├── blocks.ts                   # the 7 extended document blocks (table, image, link, toc, barcode, svg, formField)\n├── layout.ts                   # pageSize / margins / header & footer templates / compress / debug / encrypt (PdfLayoutOptions)\n├── table.ts, barcode.ts, form.ts, image.ts   # bodies shared by a dedicated tool and its inline block\n├── watermark.ts                # text and/or image watermark + position, PDF/A-1b transparency guard\n├── encryption.ts               # password + encrypt schema (Standard Security Handler), decrypt error mapping\n├── inflate-cap.ts              # PDFNATIVE_MCP_MAX_INFLATE_BYTES (engine decompression cap) + PDF_PARSE_FAILED mapping\n├── output.ts                   # sandboxed file writer / base64 emitter (single + multi)\n├── text.ts                     # newline sanitizer (Safe PDF/A)\n├── doc-features.ts             # nested lists, outline, page labels, viewer prefs (+ print-dialog defaults)\n├── pagetree.ts                 # page-tree error mapping (merge/split/extract)\n├── crypto-provider.ts          # node:crypto signing provider for DER keys (SHA-256/384/512); verification stays pure JS\n├── projection.ts               # verbosity / fields projection for the seven read tools\n├── errors.ts                   # ToolError, SecurityError, GovernanceError\n└── tools/\n    ├── generate-basic-pdf.ts\n    ├── inspect-layout.ts\n    ├── add-barcode.ts\n    ├── sign-pdf.ts\n    ├── add-ltv.ts\n    ├── timestamp-pdf.ts\n    ├── update-metadata.ts\n    ├── add-international-text.ts\n    ├── add-table.ts\n    ├── add-form.ts\n    ├── read-form-fields.ts\n    ├── fill-form.ts\n    ├── add-chart.ts\n    ├── embed-image.ts\n    ├── inspect-pdf.ts\n    ├── verify-pdf.ts\n    ├── validate-pdf.ts\n    ├── add-attachment.ts\n    ├── extract-attachments.ts\n    ├── extract-text.ts\n    ├── merge-pdfs.ts\n    ├── split-pdf.ts\n    ├── extract-pages.ts\n    ├── annotate-pdf.ts\n    ├── encrypt-pdf.ts\n    ├── decrypt-pdf.ts\n    ├── draft-governance-issue.ts\n    └── prepare-signature-placeholder.ts\nscripts/\n├── verify-issue.mjs            # governance draft checker (npm run verify:issue)\n├── validate-pdfa.mjs           # veraPDF run (npm run validate:pdfa; PASS/FAIL/XFAIL/XPASS/INFRA/SKIP)\n├── generate-pdfa-corpus.mjs    # builds the 26-file PDF/A corpus (24 validated incl. 3 negative canaries, 2 page-tree outputs)\n└── tool-shape.mjs              # structural tools/list fingerprint (--write refreshes tests/_fixtures/tool-shape.json)\n.github/workflows/ci.yml        # Linux (Node 22 / 24) + Windows quality gate\n.github/workflows/verapdf.yml   # non-blocking veraPDF CI job (SHA-256-pinned installer, VERAPDF_REQUIRED=1)\ntests/                          # vitest suites (one per tool / module; document-blocks, layout-options, inspect-layout,\n                                #   watermark, inflate-cap, catalogue-parity + catalogue-superset vs the 1.5.0 fixture)\n```\n\n---\n\n## 🗺 Roadmap\n\nv1.6.0 is shipped (full engine coverage — 13 block kinds, layout options, `inspect_layout` — PAdES LTV ladder, print production, charts v2, `update_metadata`, MCP 2026-07-28). The full plan — released milestones, in-progress work, and long-term direction — lives in [ROADMAP.md](ROADMAP.md).\n\n**Still deferred:**\n\n- `redact_pdf` — pdfnative has no content-removal API; an overlay-only \"redaction\" would create false security.\n- Native ECDSA verification — pdfnative does not export `ecdsaVerifyHash`; `verify_pdf` keeps its pure-JS path for P-256.\n- HTTP page streaming — MCP 2026-07-28 still has no partial `structuredContent`, so large results stay single-shot.\n\nHave a feature idea? Open an issue or PR.\n\n---\n\n## ⭐ Star the project\n\nIf `pdfnative-mcp` is useful to you, please ⭐ this repository — and consider also starring the underlying engine [Nizoka/pdfnative](https://github.com/Nizoka/pdfnative). Stars help others discover the project and motivate continued development.\n\n---\n\n## 🤝 Contributing\n\nContributions are very welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md), check the [open issues](https://github.com/Nizoka/pdfnative-mcp/issues), and follow the [code of conduct](CODE_OF_CONDUCT.md).\n\n---\n\n## 📄 License\n\n[MIT](LICENSE) © 2026 Nizoka\n\n`pdfnative-mcp` is built on top of [`pdfnative`](https://github.com/Nizoka/pdfnative) and the [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk).\n\n\n",
  "bytes": 57841,
  "sha": "17d6621fdb95412e3160d6836804eed7d53eb67a6734a9a277774d79be3dface",
  "repo_slug": "nizoka/pdfnative-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_nizoka_pdfnative_mcp_d42901bf/readme"
}