v1.6.0 - Full engine coverage, PAdES LTV, print production, charts v2, MCP 2026-07-28, pdfnative 1.7
Latestpdfnative-mcp v1.6.0
Released 2026-08-23
v1.6.0 aligns the server with pdfnative v1.7.0
and the MCP 2026-07-28 specification, and grows the catalogue to 28 tools.
It exposes the engine's full document model — all 13 block kinds in
generate_basic_pdf, page layout options on every document tool, build-time
encryption, image watermarks, an annotation inventory and the read-only
inspect_layout pagination preview — completes the PAdES baseline ladder
(B-B → B-T → B-LT → B-LTA) with sign_pdf timestamps, add_ltv and
timestamp_pdf, adds print production (page boxes, bleed, printer's marks,
/Trapped, /UserUnit, custom OutputIntent), charts v2 (stacked / area / scatter,
secondary axis, log and time scales), PDF/A conformance diagnostics with an honest
embedFonts switch, and update_metadata. The MCP transport moves to the SDK v2
line: 2026-07-28 clients get the stateless protocol (server/discover,
resultType, cache hints), while 2025-era hosts keep working through the automatic
legacy fallback.
No breaking changes to the tool API: every existing call keeps its inputs, error
codes and response shape, and default outputs are byte-identical except where
pdfnative 1.7.0 corrected previously-wrong output (listed under Upgrade). All new
behaviour is opt-in; a superset gate against the published 1.5.0 catalogue proves
it.
Highlights
- Full engine coverage —
generate_basic_pdfnow composes everyDocumentBlockpdfnative offers (table,image,link,toc,barcode,svg,formFieldjoinheading,paragraph,list,chart,pageBreak,spacer);pageSize,margins, runningheaderTemplate/footerTemplate,compressanddebugon the nine document tools; build-timeencryptthat keeps the AcroForm; image watermarks withposition;inspect_pdf annotations: true; and the 28th tool,inspect_layout, a pagination dry run with no PDF produced. See Full engine coverage below. - PAdES long-term validation —
sign_pdfgainsprofile: 'pades',timestamp: true(RFC 3161, B-T), RSA-SHA384/512,fieldName/allowMultiplefor several signatures andcertChainDerBase64; newadd_ltvembeds/DSS+/VRI(online through the operator-configured revocation provider or offline from caller-supplied material, B-LT); newtimestamp_pdfappends a/DocTimeStamp(B-LTA).verify_pdf ltv: truereports profile, timestamp, embedded revocation status and the achieved level. New guide:docs/guides/LTV.md. - Network charter, mirrored from pdfnative — the server still makes no outbound request by default. The only egress it can ever perform goes to the TSA / OCSP / CRL endpoints the operator configures (
PDFNATIVE_MCP_TSA_URL,PDFNATIVE_MCP_REVOCATION,PDFNATIVE_MCP_NETWORK_ALLOWED_HOSTS); URLs never come from tool arguments, and certificate-supplied OCSP/CRL URLs pass an SSRF guard (allow-list, http(s) only, no credentials, no redirects, internal address literals rejected, size caps, timeouts). - MCP 2026-07-28 — SDK v2 (
@modelcontextprotocol/server): stateless per-request envelope,server/discover,resultType,ttlMs/cacheScopehints (catalogue 24 h public, resources private), per-resultserverInfo,Mcp-Method/Mcp-Nameheaders, resource-not-found-32602, deterministictools/list. Legacyinitializeclients (2025-11-25 / 06-18 / 03-26) are served by the same binary on stdio and HTTP. - Print production —
print(TrimBox / BleedBox / ArtBox / CropBox,bleedshorthand, crop + registrationmarks,/UserUnit),metadata(/Author,/Subject,/Keywords,/Trappedwith XMP parity),outputIntent(custom RGB ICC) and print-dialogviewerPreferenceson every document tool; boxes survivemerge_pdfs/split_pdf/extract_pagesand are reported byinspect_pdf. New guide:docs/guides/PRINT.md. - Charts v2 —
stackedBar/stackedBarH/area/scatter, per-seriesxValueswith linear or UTC-deterministic time axes, a secondary right axis (axis2),axis.scale: 'log',dataLabels,labelStride/labelRotation. - Honest PDF/A — text rendered through the viewer's base-14 Helvetica is not embedded, so a PDF/A claim on such a file is rejected by veraPDF.
embedFonts: trueembeds Noto Sans Latin for a valid claim;strict: truefails instead of producing a non-conformant file;includeDiagnostics: trueechoes the engine's diagnostics. The PDF/A guide is corrected accordingly, and an advisory veraPDF corpus runs locally (npm run validate:pdfa) and in CI. update_metadata— rewrite/Info(+ XMP) of an existing PDF as an incremental update.
Full engine coverage
Every option of pdfnative 1.7's document builder that a thin wrapper can expose honestly is now reachable. All of it is opt-in and absent by default, so default output stays byte-identical; tests/catalogue-superset.test.ts compares the live catalogue with the frozen 1.5.0 one and fails on any removal or narrowing.
- 13 block kinds in
generate_basic_pdf— newtable,image,link,toc,barcode,svg,formFieldblocks (src/blocks.ts), each sharing its body with the dedicated tool (add_table,embed_image,add_barcode,add_form) so a standalone artefact and an inline block validate and render identically. Rules:linkacceptshttp:/https:/mailto:only, control characters (C0, DEL and C1) rejected;image≤ 12 M base64 characters each and a 24 MiB decoded budget per call — PNGs must be 8-bit, non-interlaced, without alpha or palette (the IHDR is read at the boundary and the unsupported variants fail withVALIDATION_ERRORand a remedy instead of an opaqueGENERATION_FAILED);svg(≤ 100 000 characters) covers<path>,<rect>,<circle>,<ellipse>,<line>,<polyline>,<polygon>,<text>/<tspan>and silently ignorestransform,<g>,<use>,<image>, gradients, opacity and CSS — no XML parser, no external reference ever fetched, entities limited to& < > " ' &#n;;tocprints a contents page from the heading blocks with internal/GoTolinks and pairs withoutline: 'auto';formFieldunder a PDF/A claim reportsPDFA_UNEMBEDDED_FORM_FONT;barcodehas noalt(engine limitation). Documents that expand to more than 50 000 engine blocks after newline splitting fail withVALIDATION_ERRORand an agent remedy (split the document) rather than the engine'sGENERATION_FAILEDnaming an option the agent cannot set. - Layout options on the nine document tools (
src/layout.ts) —pageSize(A4default,Letter,Legal,A3,Tabloid;print.*boxes must fit it),margins(all four, 0–200 pt, default 45 / 36 / 35 / 36),headerTemplate/footerTemplate(left/center/rightzones,fontSize,color; placeholders{page}{pages}{title}{date}) — afooterTemplatereplaces the default footer sofooterTextis then ignored, and{date}is the build-day wall clock, notcreationDate(avoid it for stable bytes; a cache hit returns the earlier date);compress(FlateDecode streams — smaller file, different bytes; the XMP packet stays plain under PDF/A);debug(margin / block / cell guide rectangles as unmarked content — not for PDF/UA output). - Encryption at build time —
encryptongenerate_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 (an encrypted fillable form was unreachable before —encrypt_pdfrebuilds the page tree and drops it). Exclusive withpdfA(VALIDATION_ERROR, ISO 19005-1 §6.3.2); randomised bytes, never cached. Deliberately not offered onprepare_signature_placeholder(the output must stay signable) oradd_attachment(PDF/A-3). inspect_layout(28th tool, read-only) — wrapsinspectDocumentLayout:totalPages,pageWidth/pageHeight,marginsand each block'spage/x/top/width/height(2-decimal points) for the sameblocksplus every input that moves a block (title,footerText,pdfA,normalize,embedFonts,pageSize,margins,headerTemplate,footerTemplate);verbosity: 'summary'yields{ pageWidth, pageHeight, totalPages, blockCount }. Known engine gap: atocblock is measured as 0 pt, so a document with a printed contents may paginate one page later than previewed (stated in the tool description, pinned by a test).inspect_pdf annotations: true—annotations[]with 0-basedpage,subtype,rect, and when presentcontents(truncated to 200 characters),title,color,quadPoints, linkurl;annotationCount; newcheck: 'annotations'.- Image watermarks —
watermark.image(JPEG / PNG through the shared decoder, ≤ 12 M base64 characters, own 8 MiB decoded cap,opacitydefault 0.10,width/height) ongenerate_basic_pdfandadd_table, alone or withtext(default opacity 0.15);watermark.position: 'background' | 'foreground'for both.textis no longer required. Either opacity below 1.0 is rejected underpdfa1b. - Forms and images —
add_formandformFieldblocks gainfieldType: 'listbox'andplaceholder;embed_imagegainsalignandalt(tagged/Figure /Alt).embed_image.imageBase64keeps its unbounded 1.5.0 contract; the 12 M-character cap applies to inlineimageblocks and watermark images only. PDFNATIVE_MCP_MAX_INFLATE_BYTES(src/inflate-cap.ts) — operator override of the engine's 100 MiB per-stream decompression cap (integer ≥ 1024, read once at startup; an invalid value refuses to start with one clean fatal line). A capped attachment stream failsextract_attachments includeData: truewithPDF_PARSE_FAILEDand a remedy;extract_textdegrades to empty page text for a capped content stream (engine behaviour — the per-page decode failure is swallowed upstream; candidate upstream issue).- New PDF/A diagnostics —
PDFA_UNEMBEDDED_FORM_FONT(any form field under a PDF/A claim) andPDFA_DEVICE_CMYK_IMAGE(CMYK JPEG against the sRGB OutputIntent) alongsidePDFA_NO_FONT_ENTRIES; visible withincludeDiagnostics, fatal withstrict. - Deliberately unexposed engine options (recorded in
docs/KNOWLEDGE_BASE.md): theLayoutDebugOptionsobject form,WatermarkText.autoFit, per-block font / colour overrides,TableBlock.columns,PdfParams.columns/colors/fontSizes,maxBlocks— one tool surface, byte-identical defaults, agent-facing simplicity. - Corpus —
composite-blocks-pdfa2b.pdf(every block kind under PDF/A-2b) andlayout-letter-templates-compress-pdfa2b.pdf(Letter, templates,compress) join the veraPDF corpus (26 files, 24 validated).
Added
- feat(tool): inspect_layout — read-only pagination dry run (see Full engine coverage); the seventh tool with
verbosity/fields. - feat(tool): add_ltv — PAdES B-LT Document Security Store;
mode: 'online'(operator revocation provider,REVOCATION_NOT_CONFIGUREDotherwise) ormode: 'offline'(parse-validated caller material);summaryin the structured result. - feat(tool): timestamp_pdf — PAdES B-LTA document timestamp through the operator TSA (
TSA_NOT_CONFIGUREDotherwise); the token's status, message imprint and nonce are checked before embedding (its own CMS signature is verified byverify_pdf), auto-suffixed field names for periodic re-timestamping. - feat(tool): update_metadata — incremental
/Info+ XMP rewrite (title / author / subject / keywords / pinnedmodDate); encrypted sources rejected. - feat(sign): sign_pdf —
profile,timestamp,algorithmrsa-sha384 / rsa-sha512,certChainDerBase64,fieldName,allowMultiple; errorsPLACEHOLDER_AMBIGUOUS,SIGNATURE_FIELD_NOT_FOUND,TSA_REJECTED. - feat(sign): prepare_signature_placeholder —
subFilter,reserveTimestamp. - feat(verify): verify_pdf —
/DocTimeStampentries verified as RFC 3161 tokens, per-signaturesubFilter, opt-inltvview (profile,timestamp,revocation,ltvLevel, document-leveldss/ltvLevel/caveats). - feat(inspect): inspect_pdf —
signatures: trueinventory (subFilter,isDocTimestamp,isPlaceholder,byteRange,vriKey), presence-gateddss/docTimestampCount/trapped, page boxes +userUnitunderpages: true,checkvaluesdss/docTimestamp/trapped. - feat(print):
print,outputIntent,metadata,strict,includeDiagnostics,embedFontson generate_basic_pdf, add_table (both backends), add_international_text (noembedFonts— fonts are always embedded), add_chart, add_barcode, embed_image, add_form, add_attachment, prepare_signature_placeholder;viewerPreferencesgainsduplex,pickTrayByPDFSize,printPageRange,numCopies. ErrorsPRINT_ERROR,GENERATION_FAILED;PDF_A_COMPLIANCE_VIOLATIONalso covers strict diagnostics anduserUnitunder pdfa1b. - feat(chart): charts v2 fields on
add_chartand thechartblock ofgenerate_basic_pdf; engine cross-field rules surface asCHART_ERRORwith the remedy. - feat(mcp): MCP 2026-07-28 on
@modelcontextprotocol/server^2.0.0 with legacy fallback;SERVER_CACHE_HINTS; dependency-free node:http bridge (src/http.ts); stdio smoke and 2026-era HTTP conformance tests. - feat(network):
src/network.ts— operator-configured TSA / OCSP / CRL providers with the SSRF guard; env varsPDFNATIVE_MCP_TSA_URL,PDFNATIVE_MCP_TSA_AUTH,PDFNATIVE_MCP_REVOCATION,PDFNATIVE_MCP_NETWORK_ALLOWED_HOSTS,PDFNATIVE_MCP_NETWORK_TIMEOUT_MS; errorsTSA_NOT_CONFIGURED,REVOCATION_NOT_CONFIGURED,NETWORK_HOST_NOT_ALLOWED,NETWORK_ERROR,LTV_NO_SIGNATURE,LTV_EMPTY,LTV_MATERIAL_INVALID,LTV_ERROR,METADATA_ERROR. - feat(validation): advisory veraPDF corpus —
npm run corpus:pdfa/npm run validate:pdfa(pinned veraPDF 1.30.2, Windows.batlauncher supported) and the non-blockingverapdf.ymlworkflow. See Hardening and behaviour changes for the fail-closed mode, the negative canaries and the 26-file corpus. - feat(env):
PDFNATIVE_MCP_MAX_INFLATE_BYTES— operator-set engine decompression cap (see Full engine coverage). - test: offline mock PKI (
tests/_ltv-fixtures.ts), loopback RFC 3161 server (tests/_tsa-server.ts), in-memory MCP harness (tests/_mcp-harness.ts), HTTP fixture; new suitesdocument-blocks,layout-options,inspect-layout,watermark,inflate-cap,catalogue-superset,error-codes; coverage thresholds raised to 89 / 80 / 90 / 91 (test counts under Hardening and behaviour changes). - docs: guides
LTV.md,PRINT.md; examplespades-ltv-ladder,ltv-offline,multi-signature,update-metadata,stacked-bar-chart,dual-axis-time-chart,scatter-log-chart,print-bleed-marks,pdfa-embed-fonts,inspect-layout.
Changed
- chore(deps): pdfnative
^1.6.0→^1.7.0(additive, 0 removed exports). Free wins: colour-emoji flag and ZWJ sequences render as single glyphs; Arabic ALEF joining and Persian forms, RTL digit order and paired-delimiter mirroring are now UAX #9 conformant; incremental writer / xref reader hardening. - chore(deps): MCP SDK
@modelcontextprotocol/sdk^1.29 →@modelcontextprotocol/server^2.0.0 (+@modelcontextprotocol/core);zod^4.2.0. Still three runtime dependencies; the transitive tree no longer contains hono / express / jose. - chore(api):
_meta.apiVersion1.5.0→1.6.0; package / server versions →1.6.0. - chore(governance): the charter keeps "no GitHub write path" and "no telemetry" absolute and now states the single permitted egress class: operator-configured TSA / OCSP / CRL endpoints, never a URL from a tool argument (
ai-governance.json,AGENT_RULES.md, MCP governance prompts). - chore(http): GET / DELETE on
/mcpanswer 405 (stateless serving, no SSE resumability — unchanged for the stateless mode v1.5 already used); Host / Origin loopback guard kept.
Fixed
- fix(sign):
signerName/reason/location/contactInfo/signingTimenever reached the/Sigdictionary — pdfnative < 1.7 dropped the values passed at placeholder time and wrote/M= now. They are now baked into the placeholder (sign_pdfwhen it injects one,prepare_signature_placeholderalways), soverify_pdfreports them and a pinnedsigningTimelands in/M(a pre-built placeholder keeps its own/M). - fix(verify): a
/DocTimeStampfield was parsed as a CMS signature and flippedallValidtofalseon every B-LTA document; document timestamps are now verified as RFC 3161 tokens. - fix(form):
add_form fieldType: 'textarea'(and theformFieldblock) now maps to the engine'smultilineText. 1.5.0 passed the string through unmapped, so the widget rendered as a plain single-line text field; the output now carries/Ff 4096. This changes bytes for that one input (recorded underdocs/API_STABILITY.md§5 Bug fixes that change bytes). - fix(docs): the PDF/A guide claimed every font was embedded; base-14 Helvetica text is not. See
embedFonts.
Hardening and behaviour changes
Independent read-only reviews (MCP 2026-07-28 conformance and backward compatibility, agent-consumption quality and engine coverage, factuality / tests / veraPDF) ran on the finished branch. Everything below is additive or touches error paths only; default outputs stay byte-identical and _meta.apiVersion stays 1.6.0.
Hardening
- Strict input schemas — every Zod schema is
.strict()at every nesting level: an unknown or misspelt key is aVALIDATION_ERROR("Unrecognized key"), exactly as the publishedadditionalProperties: falsealways declared (previously stripped silently). - Base64 / DER boundary (
src/base64.ts) — adata:…;base64,prefix is tolerated; PEM armour where DER base64 is expected →VALIDATION_ERRORwith the exactopenssl … -outform DERremedy; PEM text, a nesteddata:URI or double-encoded base64 passed as a PDF →PDF_PARSE_FAILEDwith a hint; an empty payload →VALIDATION_ERROR.sign_pdfcertificate / chain / key parse failures now carryVALIDATION_ERROR+ remedy (were uncoded engine text). - Output-schema conformance —
structuredContentalways validates againstoutputSchema(a 2026-07-28 MUST), includingverbosity: 'summary'andfieldsprojections: the seven read tools declare projectable schemas (every property optional,additionalProperties: falsekept, summary-only scalars declared); asserted with the SDK's Ajv 2020-12 validator (tests/schema-conformance.test.ts). - HTTP bearer token (
src/auth.ts) — opt-inPDFNATIVE_MCP_HTTP_TOKEN(≥ 16 characters, no whitespace; a weaker value aborts startup). When set,/mcprequiresAuthorization: Bearer <token>, otherwise401+WWW-Authenticate: Bearer realm="pdfnative-mcp"(witherror="invalid_token"only when credentials were sent, RFC 6750 §3.1) and a JSON-RPC-32600body; constant-time comparison, never logged. Without it the loopback endpoint has no authentication (SECURITY.md now says so and recommends the token); the startup log states the active mode. - HTTP Origin port — the SDK's loopback Origin check is port-agnostic; the server additionally requires the
Originport to equal its own port (1.5.0 pinnedhttp://127.0.0.1:<port>the same way). - HTTP keep-alive — client-disconnect detection moved to the per-request response; long-lived connections no longer accumulate socket listeners (
MaxListenersExceededWarning). - Response cache —
sign_pdfis never cached (every call, not onlytimestamp: true), nor is any document call carryingencrypt; the key is namespaced byTOOL_API_VERSION/package versionso an engine upgrade never serves old bytes; a hit carries_meta.cached: true(and the earlier{date}placeholder, if any). - Compatibility gate —
tests/_fixtures/tool-shape.v1.5.0.jsonfreezes the published 1.5.0 catalogue andtests/catalogue-superset.test.tsfails on any removal or narrowing (tool, property, enum value, default, newrequired, tighter bound); the accepted deltas (langoneOf→anyOf,watermark.textno longer required,sign_pdf openWorldHint) are enumerated and must each still occur. It caught a bound that had crept ontoembed_image.imageBase64; that input is unbounded again. - Configuration errors — a weak token or an invalid
PDFNATIVE_MCP_MAX_INFLATE_BYTESprints one cleanfatal:line (no stack) and exits 1. - Lint —
npm run lintiseslint src --max-warnings 0; the non-null assertions insrc/were replaced by explicit narrowing (a CMS structure shorter than the parser expects now raises a codedCMS_PARSE_FAILED). - Protocol errors —
tools/callwith an unknown tool name is a JSON-RPC-32602error ([UNKNOWN_TOOL] Unknown tool: …) instead of anisErrorresult, as the specification classifies it (callToolDirectkeepsisError).serverInfocarrieswebsiteUrl; the resource template ispdfnative://output/{+path}(RFC 6570 reserved expansion);server.jsondescription trimmed to the registry's 100-character limit. - Honest wording — three runtime dependencies (pdfnative, the MCP SDK, zod); "constant-time
node:crypto" applies to signing DER keys only (raw P-256 scalars and all verification are pure JS); a timestamp token is checked for status, imprint and nonce before embedding and its own signature verified byverify_pdf; a/DocTimeStampcounts inallValidlike any signature (the earlier "never flipsallValid" wording was wrong).
New opt-in inputs (byte-identical when absent)
creationDate(ISO-8601) on all nine document tools —generate_basic_pdf,add_table,add_form,add_international_text,embed_image,add_barcode,add_attachment,add_chart,prepare_signature_placeholder— pins/Info /CreationDate, the XMP dates and therefore the trailer/ID.signingTimeonprepare_signature_placeholder(/Sig /Mfrozen at placeholder time);sign_pdf.signingTimenow accepts time-zone offsets (2026-01-15T10:00:00+01:00).- Pinned dates give byte-identical output on the same host time zone (the engine serialises local time, e.g.
D:20260115100000+01'00'); TSA tokens, onlineadd_ltv,encrypt_pdfand ECDSA signatures are never reproducible.rsaKeyPkcs1DerBase64accepts PKCS#8 DER as well as PKCS#1.
Behaviour changes (error and opt-in paths only)
validate_pdfon unparsable input →PDF_PARSE_FAILED(was avalid: falseresult carryingUnparseable PDF: …).inspect_pdfcheckscontains only the requested keys (was every key,falsewhen unrequested);check: 'signed'is true when at least one signature field carries signed content — an extra unsigned placeholder no longer negates it.verbosity: 'summary'keepsdocTimestampCount/trapped/checksPassed(inspect_pdf) andltvLevelwithltv: true(verify_pdf);fieldspaths that match nothing are reported in_meta.unmatchedFields+_meta.availableFields.- Page-index / range errors from
merge_pdfs/split_pdf/extract_pages→VALIDATION_ERRORwith a 0-based hint (wasPDF_PARSE_FAILED). FORM_FIELD_NOT_FOUNDnamesread_form_fields/onUnknownField: 'ignore';ENCRYPTED_SOURCEremedies are tool-specific (annotate_pdf/update_metadata:decrypt_pdf→ edit →encrypt_pdf;add_ltv/timestamp_pdf: decrypting would destroy the signatures — sign before encrypting). The page-tree tools never raiseENCRYPTED_SOURCE(they takepassword); their descriptions said otherwise.add_international_text.langusesanyOf(wasoneOf, same accepted values).
Catalogue size and compaction
tools/listis ≈ 245 kB (1.5.0: ≈ 108 kB). The descriptions and examples were compacted (≈ 206 kB → ≈ 174 kB at that point), but advertising every block kind inline — the 13-memberblocksunion appears in bothgenerate_basic_pdfandinspect_layout— plus the layout /encryptfragments on ten tools outweighs it. There is no$ref/$defsby policy (hosts that forwardinputSchemato function-calling APIs), so this is the honest number;SERVER_INSTRUCTIONSis ≈ 6.7 kB (from 12.9 kB); at most two_meta.examplesper tool, all executable against theirinputSchema(three were not); the decision tree resolvesadd_attachmentvsgenerate_basic_pdf,inspect_pdf check:'signed'(structural) vsverify_pdf(cryptographic),sign_pdfvsprepare_signature_placeholder, and points toinspect_layoutfor "will it fit".- New recipe prompts
pades_ladder,print_ready,reproducible_output,pdfa_valid(with the existinggovernance_contract,draft_issue_workflow). - Parity gate:
scripts/tool-shape.mjs(+--write) fingerprints the catalogue with description strings stripped;tests/catalogue-parity.test.tscompares it withtests/_fixtures/tool-shape.json. Wording may change freely; a structural change is a deliberate fixture refresh reviewed underdocs/API_STABILITY.md§5 (which now states thattools/listwording, examples and instructions are outside the byte-identical charter).
CI / veraPDF
scripts/validate-pdfa.mjsreportsPASS/FAIL/XFAIL/XPASS/INFRA/SKIPper file;VERAPDF_REQUIRED=1fails closed (missing veraPDF / Java, crash or unparseable report → exit 3) and the workflow sets it; infrastructure failures are no longer reported as conformance failures.- Corpus grown to 26 files (24 claiming PDF/A incl. 3 negative canaries with
expectCompliant: false, 2 page-tree outputs skipped) — 1b watermark, customoutputIntent, emoji + math under 2u,add_form, unsigned and PAdES-signed placeholders, PDF attachment under 3b,update_metadataon a claiming file, merge / extract, a composite document with every block kind, a Letter document with templates andcompress. Last run: 21 PASS / 3 XFAIL / 2 SKIP. - The veraPDF installer zip is SHA-256-pinned in
verapdf.yml; the job stays non-blocking in 1.6.0.ci.ymladds a Windows job (Node 22) next to the Linux 22 / 24 matrix. - Known engine limitations (documented in AGENTS.md,
docs/guides/PDFA.md,docs/KNOWLEDGE_BASE.md):add_form/formFieldblocks withpdfA+embedFonts: truestill fail PDF/A-2b under veraPDF — the AcroForm/DR /Helvis an unembedded Type1 font (ISO 19005-2 6.2.11.4.1; candidate upstream issue viadraft_governance_issue, draft generated); an unsignedprepare_signature_placeholderoutput withpdfAis conformant only once signed (ISO 19005-2 6.4.3);inspectDocumentLayoutmeasures atocblock as 0 pt;extractTextswallows per-page decode failures under the inflate cap. - Docs corrected across README / AGENTS / guides / llms.txt: full 25-code
langlist, seven (not four) read tools takeverbosity/fields,examples/run.mjsnever existed,EXTRACTION_UNSUPPORTEDis never raised,@modelcontextprotocol/server(not/sdk), error reference lists every emitted code (45) plus the[UNKNOWN_TOOL]protocol error. - Build no longer ships
.mapfiles (tsconfig.build.json): the npm package is 134 files / 285.9 kB packed.package.jsoncarries a plain-text description and 66 curated keywords. Measured on the final branch: 69 test files, 937 tests passed + 1 skipped; coverage 93.06 % statements / 84.68 % branches / 98.59 % functions / 95.09 % lines.
Deferred by design
- redact_pdf stays deferred (overlay / flatten ≠ content removal; pdfnative 1.7.0 exports no content-removal API).
verify_pdfkeeps its local P-256 ECDSA verifier (ecdsaVerifyHashstill not exported). Per-tool HTTP page streaming remains blocked: MCP 2026-07-28 still has no partialstructuredContentenvelope. - The opt-in telemetry hook stays a long-term item and is intentionally not part of this release.
Install
npm install pdfnative-mcp@1.6.0
Upgrade
No breaking changes. Drop-in replacement for v1.5.0. Deliberate behaviour changes inherited from pdfnative 1.7.0 — in each case the previous output was wrong or non-conformant:
- RTL text (
add_international_text): digit runs keep logical order, paired delimiters mirror, ALEF joins correctly and Persian letters take positional forms — every Arabic-script document renders differently, and correctly. - Forms (
add_form,fill_form): the AcroForm/Helvfont carries/ToUnicodein every mode (form text becomes searchable); all form outputs change bytes. - Tagged / PDF/A documents on base-14 fonts (
pdfAon the document tools): the shared WinAnsi/ToUnicodeCMap is emitted; bytes change. The same configuration now raises thePDFA_NO_FONT_ENTRIESdiagnostic — silent by default, visible withincludeDiagnostics, fatal withstrict; fix it withembedFonts: true. - Incremental outputs (
prepare_signature_placeholder,sign_pdf,annotate_pdf,fill_form, the new LTV tools):/ID[1]is regenerated per revision and an EOL is inserted before the appended revision; earlier revisions stay a byte-exact prefix. - CMS signatures: signed attributes are encoded in canonical DER order; signatures remain valid.
- Charts whose x labels previously overlapped now draw every Nth label;
labelStride: 1restores the old draw-everything behaviour. - Colour emoji (
add_international_textwithemoji): COLRv1PaintCompositelayers are now rendered, so flag sequences draw as flat flags instead of tofu — output bytes change for every document containing emoji input. draft_governance_issue: theHUMAN_GATEcharter sentence was reworded to state the single permitted egress class (operator-configured TSA / OCSP / CRL, never GitHub), so the draft markdown andcomplianceReport.humanGatetext differ from v1.5.0. Deliberate charter update; the report shape is unchanged.sign_pdfplaceholder size: the default reservation is nowmax(16384, estimateContentsSize(cert, algorithm))instead of a flat 16384 bytes, plus 8192 bytes whentimestamp: true— identical for signer certificates up to roughly 10 KB, larger (different bytes) beyond, where the old size risked an overflow. To pin it, build the placeholder withprepare_signature_placeholderand itsplaceholderBytesinput (sign_pdfhas no such input), then sign that document.add_formtext areas:fieldType: 'textarea'now produces a real multi-line field (/Ff 4096); bytes change for that input only.- MCP transport: hosts that speak MCP 2026-07-28 now receive
resultType, cache hints and_meta.serverInfo; 2025-era hosts see no difference. Programmatic consumers ofcreateServer()now receive an@modelcontextprotocol/serverServerinstance.
Migrating from 1.5.0
Every 1.5.0 input is still accepted and every 1.5.0 tool, property, enum value and bound is still there (tests/catalogue-superset.test.ts). Six things a 1.5.0 client may have relied on behave differently — all on error paths or on inputs the published schema already declared invalid:
- Stray keys fail. A call carrying an unknown or misspelt key, top-level or nested (
validate_pdf { profile: 'pdfa2b' }, ablocks[]entry with an extra property), was silently stripped in 1.5.0 and is nowVALIDATION_ERROR("Unrecognized key"). The schemas always saidadditionalProperties: false. - Page-index mistakes are
VALIDATION_ERROR. Out-of-range pages / ranges onmerge_pdfs,split_pdf,extract_pageswerePDF_PARSE_FAILED; a client catching that code for this case must catchVALIDATION_ERROR(the message carries a 0-based hint). validate_pdfon unparsable bytes is an error, not a verdict. 1.5.0 returned{ valid: false, errors: ['Unparseable PDF: …'] }as a successful result; 1.6.0 returnsisError: true[PDF_PARSE_FAILED]with nostructuredContent. A parse failure is not a PDF/UA verdict — branch onisErrorfirst.- Unknown tool or prompt names are protocol errors.
tools/call/prompts/getwith a name that does not exist → JSON-RPC-32602([UNKNOWN_TOOL]/[UNKNOWN_PROMPT]), noisErrorresult. inspect_pdf.checksholds only the keys you asked for. 1.5.0 echoed every check name withfalse; readchecksPassedor the requested key, never an absent one.add_formtext areas change bytes.fieldType: 'textarea'now sets the multiline flag (see Fixed).
Unchanged and worth knowing: a stdio request sent before initialize is dropped and JSON-RPC batch arrays are not accepted on stdio (SDK behaviour in 1.5.0 too; HTTP batches work); embed_image.imageBase64 has no length bound; encrypt_pdf / decrypt_pdf still drop signatures and the AcroForm — use the new build-time encrypt when the form must survive.
Links
- CHANGELOG: ../CHANGELOG.md
- Full diff: v1.5.0...v1.6.0
- pdfnative 1.7.0 release notes: https://github.com/Nizoka/pdfnative/blob/main/release-notes/v1.7.0.md
- MCP 2026-07-28 changelog: https://modelcontextprotocol.io/specification/2026-07-28/changelog