Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f64aabc
Extract bounded accessibility inspection
x1-wealth Aug 4, 2026
30901e6
Isolate read-only accessibility inspection
x1-wealth Aug 4, 2026
f8fb9a2
Expose bounded accessibility inspection
x1-wealth Aug 5, 2026
71e4cf0
Clarify v0.9.3 installed proof
x1-wealth Aug 5, 2026
e097101
Update accessibility share contract count
x1-wealth Aug 5, 2026
1bc0415
Document bounded accessibility inspection
x1-wealth Aug 5, 2026
42f5575
Bind live artifact checks to accessibility discovery
x1-wealth Aug 5, 2026
48068f7
Update exhaustive tool annotation audit
x1-wealth Aug 5, 2026
0b79d02
Prove accessibility in host smoke harnesses
x1-wealth Aug 5, 2026
cf57683
Bind accessibility module in Phase 1 evidence
x1-wealth Aug 5, 2026
2ed1572
Isolate macOS system render workspaces
x1-wealth Aug 5, 2026
be89f92
Drain PDF renderer workspaces on shutdown
x1-wealth Aug 5, 2026
f9a1286
Hold renderer gate through shutdown drain
x1-wealth Aug 5, 2026
026e727
Persist terminal PDF renderer shutdown state
x1-wealth Aug 5, 2026
325da6d
Bind renderer admission to controller shutdown
x1-wealth Aug 5, 2026
8830b4e
Merge current master into accessibility inspection
silverstein Aug 5, 2026
81749d4
Raise comparison baseline hang ceiling
silverstein Aug 5, 2026
cbeb138
Refresh integrated accessibility contract evidence
silverstein Aug 5, 2026
6f05bf8
Integrate v0.9.5 master into accessibility candidate
silverstein Aug 5, 2026
e6441f5
Prepare accessibility candidate v0.9.6
silverstein Aug 5, 2026
9d3c377
Integrate final v0.9.5 stability master
silverstein Aug 5, 2026
f0e23e5
test: isolate resource-heavy suites
silverstein Aug 5, 2026
34ca7e0
test: cover remaining contended suites
silverstein Aug 5, 2026
dff3449
Integrate master into the v0.9.6 candidate
silverstein Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ separate example to locate.
- `example-fw9.pdf`: Sample form for smoke tests. Keep anonymized assets only.

### Tools currently shipped
- `display_pdf`, `list_pdfs`, `read_pdf_fields`, `fill_pdf`, `bulk_fill_from_csv`, `save_profile`, `load_profile`, `list_profiles`, `fill_with_profile`, `extract_to_csv`, `validate_pdf`, `read_pdf_content`, `read_pdf_layout`, `convert_pdf_to_markdown`, `get_pdf_info`, `compare_pdfs`, `render_pdf_page`, `render_pdf_region`, `get_pdf_resource_uri`, `read_pdf_bytes` (app-only).
- `display_pdf`, `list_pdfs`, `read_pdf_fields`, `fill_pdf`, `bulk_fill_from_csv`, `save_profile`, `load_profile`, `list_profiles`, `fill_with_profile`, `extract_to_csv`, `validate_pdf`, `read_pdf_content`, `read_pdf_layout`, `convert_pdf_to_markdown`, `get_pdf_info`, `inspect_pdf_accessibility`, `compare_pdfs`, `render_pdf_page`, `render_pdf_region`, `get_pdf_resource_uri`, `read_pdf_bytes` (app-only).

`get_pdf_info` returns bounded source-bound observations. Widget annotations
belong to form fields; ordinary annotations remain separate and their targets
Expand All @@ -25,6 +25,12 @@ rotation, and UserUnit, not MediaBox-relative signing coordinates. The macOS
system renderer uses the same view mapping for whole pages and regions and
reports raw pixels unavailable.

`inspect_pdf_accessibility` performs a bounded, local, read-only review of
exactly eight shallow catalog-level signals in an unencrypted PDF. It binds
observations to the source SHA-256, distinguishes missing from unavailable
signals, and always requires human review. It does not establish PDF/UA,
WCAG, certification, legal, or document-accessibility conclusions.

`compare_pdfs` performs a bounded, local, read-only whole-document comparison
of two PDFs with at most 20 pages each. It binds observations and evidence to
both immutable source envelopes, keeps seven coverage channels separate,
Expand Down
27 changes: 17 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,17 @@ qualification. Then perform manual host runs against `example-fw9.pdf`:
19. **rotate_pdf_pages** - Rotate pages by 90, 180, or 270 degrees
20. **reorder_pdf_pages** - Rearrange the pages of a PDF into a new order
21. **get_pdf_info** - Get source-bound page geometry, bounded metadata, form widgets, and inert ordinary annotations with explicit coverage
22. **compare_pdfs** - Compare two immutable PDFs across semantic, text, structure, form, annotation, metadata, and visual channels with source-bound evidence
23. **apply_page_plan** - Reorder, rotate, and delete pages in one pass (saves as new file)
24. **get_page_analysis** - Analyze pages for blank detection, orientation, text content, images
25. **fetch_pdf_from_url** - Download a PDF from a URL to the user's local machine (bypasses Claude's WebFetch sandbox)
26. **create_signature** - Save a reusable typed or image signature
27. **list_signatures** - List saved signatures
28. **add_signature_field** - Draw a "Sign here" placeholder box (does NOT sign)
29. **apply_signature** - Stamp a saved signature at a location (requires explicit human intent; see Signature Architecture below)
30. **prepare_signing_packet** - Fill form + add sign-here boxes in one pass
31. **detect_signature_zones** - Locate signature, initials, printed-name, and date zones with coordinates. Use apply_signature for signatures and initials, and apply_text for names and dates.
22. **inspect_pdf_accessibility** - Inspect exactly eight shallow catalog-level accessibility signals with source binding, bounded abstention, and required human review
23. **compare_pdfs** - Compare two immutable PDFs across semantic, text, structure, form, annotation, metadata, and visual channels with source-bound evidence
24. **apply_page_plan** - Reorder, rotate, and delete pages in one pass (saves as new file)
25. **get_page_analysis** - Analyze pages for blank detection, orientation, text content, images
26. **fetch_pdf_from_url** - Download a PDF from a URL to the user's local machine (bypasses Claude's WebFetch sandbox)
27. **create_signature** - Save a reusable typed or image signature
28. **list_signatures** - List saved signatures
29. **add_signature_field** - Draw a "Sign here" placeholder box (does NOT sign)
30. **apply_signature** - Stamp a saved signature at a location (requires explicit human intent; see Signature Architecture below)
31. **prepare_signing_packet** - Fill form + add sign-here boxes in one pass
32. **detect_signature_zones** - Locate signature, initials, printed-name, and date zones with coordinates. Use apply_signature for signatures and initials, and apply_text for names and dates.

### Current Extraction Boundary

Expand All @@ -153,6 +154,12 @@ Annotation URLs, destinations, and actions are never opened. Render results
report the source identity, page geometry, coordinate spaces, renderer policy,
PNG digest, and native raw-pixel digest availability.

`inspect_pdf_accessibility` reads an unencrypted PDF without editing it and
reports exactly eight shallow catalog-level signals as observed, missing, or
unavailable. Machine validation remains `not_run`, human review is required,
and PDF/UA, WCAG, certification, legal, and document-accessibility conclusions
remain `not_established`.

`render_pdf_region` uses top-left PDF.js viewport points after CropBox,
rotation, and UserUnit. Do not pass MediaBox-relative signature-zone or signing
coordinates to it. The macOS Quick Look fallback renders whole pages and
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ content, so the complete workflow is not necessarily zero egress.
- Extract structured data to CSV
- Inspect page-level details like orientation, text presence, images, and likely blank pages
- Review source-bound page geometry, bounded metadata, form widgets, ordinary annotations, and file identity
- Inspect exactly eight shallow catalog-level accessibility signals with explicit missing or unavailable states and required human review

PDF Tools does not currently bundle an OCR engine. Text reads use the PDF.js
text layer. If the selected `read_pdf_content` result contains no text, the tool
Expand All @@ -114,6 +115,11 @@ returns link or action targets only as inert values. Page and region renders
report page geometry, coordinate spaces, renderer policy, the PNG SHA-256,
and raw RGBA SHA-256 availability.

`inspect_pdf_accessibility` is a bounded structural-review screen for an
unencrypted local PDF. It does not run veraPDF, assess tag semantics or
assistive-technology behavior, or establish PDF/UA, WCAG, certification,
legal, or document-accessibility conclusions.

Region-render inputs are top-left PDF.js viewport points after CropBox,
rotation, and UserUnit. They are not MediaBox-relative signing-zone
coordinates. The macOS Quick Look fallback renders whole pages and regions in
Expand Down Expand Up @@ -219,6 +225,7 @@ that same view and reports raw pixels unavailable.
- `extract_to_csv`
- `get_pdf_identity`
- `get_pdf_info`
- `inspect_pdf_accessibility`
- `get_page_analysis`

### Active Document and Host Helpers
Expand Down
13 changes: 8 additions & 5 deletions docs/MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ mcp__<display_name, spaces underscored, non [A-Za-z0-9_-] stripped>__<tool_name>
Identifiers over **64 characters** fail in the host. This shipped as a real
defect (issue #44): the original benefit-led directory title
`PDF Tools - Fill, Sign, Merge, Split, Extract` normalizes to 41 characters and
pushes 13 of the current 40 tool identifiers past the ceiling.
pushes 14 of the current 41 packed tool identifiers past the ceiling.

The naming strategy is therefore **dual**:

Expand All @@ -261,11 +261,11 @@ The naming strategy is therefore **dual**:

Budgets are computed by `scripts/tool-identifier-budget.mjs` and gated in
`test/mcp-contract.test.js`. Current margins against the longest tool name
(`convert_pdf_to_markdown`, 23 characters):
(`inspect_pdf_accessibility`, 25 characters):

- `PDF Tools`longest identifier 39, headroom 25
- `PDF Tools: Fill, Sign & Edit`longest identifier 55, headroom 9
- Original long titlelongest identifier 71, 13 identifiers over the limit
- `PDF Tools`: longest identifier 41, headroom 23
- `PDF Tools: Fill, Sign & Edit`: longest identifier 57, headroom 7
- Original long title: longest identifier 73, 14 identifiers over the limit

**The trap when adding a tool.** The shipped short brand has generous headroom,
so a new long tool name will not break it and every host-facing check stays
Expand Down Expand Up @@ -660,6 +660,9 @@ Run these after any tool or packaging change:
- `get_pdf_info` on example-fw9.pdf - verify exact source SHA-256, page geometry, form widgets, ordinary-annotation separation, and coverage
- `get_pdf_info` on a non-form PDF - verify empty supported form and annotation channels rather than fabricated unavailability
- `get_pdf_info` on an encrypted PDF - verify typed missing/wrong password errors do not expose password or document observations
- `inspect_pdf_accessibility` on normal, explicit-false, and absent-signal fixtures: verify eight ordered checks, exact source binding, fixed `not_established` conclusions, required human review, and no file change
- `inspect_pdf_accessibility` on malformed non-PDF input: verify a partial or indeterminate result, seven unavailable checks, and no parser diagnostic or path disclosure
- `inspect_pdf_accessibility` on encrypted input: verify the fixed error, no findings, and no password or path disclosure
- `render_pdf_page` and `render_pdf_region` - verify the PNG SHA-256 against returned image bytes and raw-pixel availability against the renderer
- Render a fixture with nonzero MediaBox/CropBox origins, rotation, and UserUnit - verify native regions use PDF.js viewport points and the macOS system path fails closed
- Precede one required/read-only field with 500 orphan Widget annotations - verify the real field retains flags `3`, every widget is counted, and coverage is partial
Expand Down
17 changes: 13 additions & 4 deletions docs/MCP_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ template discovery is also unsupported and deterministically returns JSON-RPC

### Tools

The runtime returns 41 uniquely named tools. Every tool has an object input
The runtime returns 42 uniquely named tools. Every tool has an object input
schema plus `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, and
`openWorldHint` annotations. Annotations are user-interface hints, never an
authorization boundary; path allowlists and signature-intent checks remain the
Expand All @@ -36,7 +36,7 @@ every tool in both runtime copies. The handler evidence and classification
rules are recorded in
[`TOOL_ANNOTATION_AUDIT_2026-07-21.md`](TOOL_ANNOTATION_AUDIT_2026-07-21.md).

The source manifest lists all 41 tools. The packed MCPB manifest lists the 40
The source manifest lists all 42 tools. The packed MCPB manifest lists the 41
normal model-workflow tools and omits `read_pdf_bytes`, whose runtime metadata
marks it `ui.visibility: ["app"]`. `tools_generated: true` explicitly tells MCPB
hosts that runtime discovery includes an additional tool. That visibility hint
Expand All @@ -45,7 +45,7 @@ a generic MCP client can still discover and call `read_pdf_bytes`. It is not an
authorization or confidentiality boundary. Filesystem allowlists and the tool's
bounded reads remain the enforced controls.

Thirty-seven tool handlers advertise strict `outputSchema` contracts and return
Thirty-eight tool handlers advertise strict `outputSchema` contracts and return
`structuredContent`. They also return a human-readable `content` text block so
non-Apps and older clients remain usable. Successful structured output is
validated before it leaves the server, with separate generic and tool-specific
Expand All @@ -60,6 +60,15 @@ decrypt the PDF, so an encrypted document can be identified before a password
is available. Structured failures distinguish path denial, unavailable files,
invalid PDF headers, oversized inputs, and retryable identity races.

`inspect_pdf_accessibility` performs a local read-only structural review of an
unencrypted PDF. It reports exactly eight shallow catalog-level signals, one
source descriptor with SHA-256, and no output files. Each signal is observed,
missing, or unavailable with bounded reason codes. Machine validation is fixed
at `not_run`, human review is required, and PDF/UA, WCAG, certification, legal,
and document-accessibility conclusions remain `not_established`. Encrypted
inputs return a fixed abstention without findings. The tool does not run
veraPDF or assess tag semantics or assistive-technology behavior.

The trajectory harness preserves `tool-contracts.v1.json` and
`tool-contracts.v2.json` for their frozen evidence. The v2 jobs remain bound to
the reviewed v2 40-tool projection that introduced `get_pdf_identity`.
Expand All @@ -68,7 +77,7 @@ exact-output-identity preconditions. New evaluation suites must bind v3
explicitly. The grader selects the allowlisted contract and trust registry
declared by each suite, so historical evidence remains valid under its original
stack and is not silently rescored. The six existing trajectory jobs do not
constitute behavioral trajectory coverage of all 41 tools.
constitute behavioral trajectory coverage of all 42 tools.
`get_pdf_identity` is covered by its contract, handler, filesystem-race, and
agent-workflow tests rather than by those six retained jobs.

Expand Down
9 changes: 8 additions & 1 deletion docs/OUTPUT_SCHEMAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ an `isError` result is never forced through a success schema.
| `get_page_analysis` | bounded page analysis with explicit provenance, operator counts, and a `classification` rollup (`document_kind`, typed `pages_needing_vision`, explicit `pages_not_analyzed`) |
| `get_pdf_identity` | parser-independent canonical path, byte length, and SHA-256 |
| `get_pdf_info` | bounded source-bound page, metadata, form-widget, and inert annotation observations with typed coverage, exact accounting, and a full-envelope digest |
| `inspect_pdf_accessibility` | source-bound eight-signal structural review with bounded observation and reason codes, fixed limitations, required human review, and `not_established` conclusions |
| `get_pdf_resource_uri` | resource URI and local file metadata |
| `list_signatures` | saved signature summaries, including an empty array |
| `load_signature` | signature metadata and optional preview |
Expand Down Expand Up @@ -66,6 +67,12 @@ wire contract that discovery does not publish.
invalid PDF header, input over 250 MiB, and a file or pathname that changed
during hashing. Path-policy denial remains the shared structured error.

`inspect_pdf_accessibility` returns bounded partial or indeterminate results
for malformed input and a fixed, path-free error for encrypted input. The
encrypted branch exposes no findings. Shared path-policy, file-availability,
source-identity, and isolated-resource errors remain structured and do not
include parser diagnostics or passwords.

`compare_pdfs` has stable structured failures for page-cap refusal, source
identity races, password requirements or rejection, unsupported parsing,
filesystem policy denial, unavailable inputs, output-cap refusal, and internal
Expand All @@ -90,7 +97,7 @@ before loading the target PDF, writing output, or changing active-document
state.

The executable source of truth is `server/output-schemas.js`. The MCP contract
tests assert this complete 37/4 matrix, compile every schema through the pinned
tests assert this complete 38/4 matrix, compile every schema through the pinned
SDK validator, reject newer unsupported JSON Schema keywords, exercise live
success and error branches, and require byte-identical source/share runtime
files.
34 changes: 27 additions & 7 deletions docs/TOOL_ANNOTATION_AUDIT_2026-07-21.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# MCP tool-annotation audit 2026-07-21
# MCP tool-annotation audit: 2026-07-21

This is the handler-by-handler evidence behind PDF Tools' 41 MCP
`ToolAnnotations`. It covers the source runtime and the byte-identical share
runtime. It is a risk declaration for host UX, not an authorization boundary.
This living audit is the handler-by-handler evidence behind PDF Tools' current
42 MCP `ToolAnnotations`. It covers the source runtime and the byte-identical
share runtime. It is a risk declaration for host UX, not an authorization
boundary. The original 2026-07-21 verification numbers remain below as
historical evidence for the then-current 41-tool surface.

## Semantics used

Expand Down Expand Up @@ -70,6 +72,7 @@ not idempotent.
| `rotate_pdf_pages` | F | T | T | F | Deterministically writes rotated output and can replace an existing destination. |
| `reorder_pdf_pages` | F | T | T | F | Deterministically writes reordered output and can replace an existing destination. |
| `get_pdf_info` | T | F | T | F | Reads local file and PDF metadata. |
| `inspect_pdf_accessibility` | T | F | T | F | Reads eight bounded catalog-level signals from one local PDF in isolated memory and retains no output. |
| `compare_pdfs` | T | F | T | F | Reads and compares two bounded local PDFs in memory without retaining output or mutating either source. |
| `apply_page_plan` | F | T | T | F | Deterministically writes reordered/rotated/subset output and can replace an existing destination. |
| `get_page_analysis` | T | F | T | F | Reads and analyzes local PDF pages without saving mutations. |
Expand All @@ -86,7 +89,7 @@ not idempotent.

## Regression proof

`test/mcp-contract.test.js` contains the same exhaustive 41-tool matrix and
`test/mcp-contract.test.js` contains the same exhaustive 42-tool matrix and
compares all four effect hints for both runtime copies after live MCP
discovery. It also binds the complete discovery payload to an updated SHA-256,
so a title, description, schema, metadata, or annotation change requires
Expand All @@ -97,15 +100,16 @@ This matrix deliberately makes no claim that annotations enforce policy. Path
allowlists, PDF mutation guards, signing-intent validation, backups, and host
approval remain the actual controls.

Verification on Node `22.22.3`:
Historical verification recorded on 2026-07-21 with Node `22.22.3` for the
then-current 41-tool surface:

- focused live source/share contract: 1 file, 30 tests passed;
- full suite: 27 files, 308 tests passed;
- shuffled suite (`--sequence.seed=410`): 27 files, 308 tests passed;
- source/share runtime byte comparison: identical;
- dependency manifests and the protected `pdfjs-dist` pin: unchanged.

## Addendum 2026-08-03 (extraction-intelligence epic, pdf-toolkit-mcp-zyx)
## Addendum: 2026-08-03 (extraction-intelligence epic, pdf-toolkit-mcp-zyx)

The epic extended the structured outputs of `read_pdf_layout`,
`convert_pdf_to_markdown`, `get_page_analysis`, and `read_pdf_content`
Expand All @@ -115,3 +119,19 @@ normalizations) and added the optional `compact` input argument to
no `readOnlyHint`, `destructiveHint`, `idempotentHint`, or `openWorldHint`
value changes. The advertised tool-contract digest changes are recorded with
dated entries in `test/mcp-contract.test.js`.

## Addendum: 2026-08-05

The current candidate adds `inspect_pdf_accessibility` as a local, bounded,
read-only operation. Its annotation tuple is `R=T, D=F, I=T, O=F` because the
handler reads one allowlisted PDF, retains no output, and has no network or
other open-world effect.

Focused integrated-candidate evidence on 2026-08-05:

- 15 Vitest files: 199 tests passed and 6 intentionally skipped;
- `test/mcp-contract.test.js`: all 43 tests passed within that run;
- live MCP discovery: 42 unique tools, including 38 structured tools;
- complete discovery SHA-256: `d9c225a5b72694d73dc0064a28fecf76a5bedf27121b04e656b86f055ced9313`;
- source/share runtime byte comparison: identical;
- dependency versions: `pdf-lib` 1.17.1 and `pdfjs-dist` 5.4.624 unchanged.
2 changes: 1 addition & 1 deletion docs/evidence/v0.9.3-installed-proof-2026-08.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PDF Tools v0.9.3 installed-copy proof 2026-08
# PDF Tools v0.9.3 installed-copy proof: 2026-08

The public GitHub release assets were downloaded again before installation.

Expand Down
Loading