oxidize-pdf
Use oxidize-pdf's local MCP server to inspect, extract, convert, create, edit, secure, and validate PDFs inside a configured workspace, with MCP resources and prompts for agent workflows.
Read metadata and page details from PDFs.
Extract plain text, positioned text runs, Markdown, chunks, or RAG-friendly content.
Analyze validity, corruption, PDF/A compliance, or compare two PDFs.
Split, merge, rotate, reverse, extract pages, or overlay PDFs.
Add sticky-note or highlight annotations.
Create, fill, read, or validate simple form fields, with documented limitations.
Encrypt PDFs, inspect permissions, or verify signatures.
Start in-memory PDF creation sessions, add text/pages, and save to disk.
Access MCP resources such as fonts, page sizes, capabilities, version, workspace files, and session data.
Use guided prompts for summarization, extraction, form filling, and similar workflows.
Operate under workspace sandboxing and resource limits such as file size, page count, output size, sessions, and timeouts.
Integrates with GitHub Copilot's agent mode in VS Code, exposing PDF manipulation tools for AI-assisted PDF workflows.
Integrates with the OpenAI Agents SDK, allowing AI agents to perform PDF operations such as reading, extracting, converting, and manipulating PDFs.
oxidize-pdf
Documentation · Context7 · Documentation publishing and indexing
Version 0.23.4 pins Rust core 5.5.6. See compatibility and validation.
Rust-powered PDF library for Python. Generate, parse, split, merge, and manipulate PDFs with native performance. Ships with a built-in MCP server so AI agents can work with PDFs out of the box.
No C dependencies. No Java. No subprocess calls.
Installation
pip install oxidize-pdf # Core library
pip install "oxidize-pdf[mcp]" # + MCP server for AI agentsPlatforms: Linux (x86_64, aarch64) | macOS (x86_64, Apple Silicon) | Windows (x86_64) Requires: Python 3.10+
Version 0.23.4 pins Rust core 5.5.6, using the MIT-licensed oxidize-webpki
RustCrypto provider for certificate verification. See the
dependency policy for supported builds and limits.
0.23.4 release notes describe the signed-field permission fixes.
New APIs include explicit text recovery, non-breaking-space normalization,
build identification controls and incremental signature slots. See the
API guide and validation.
The current develop branch contains post-release parity work. See the current capability inventory and Python/.NET comparison for the audited subset, missing bindings and inherited core limitations. Neither the core version nor the type stubs establish full functional parity; no global coverage percentage is claimed.
Related MCP server: PDFSizeAnalyzer-MCP
Why oxidize-pdf?
oxidize-pdf | Pure-Python libs | C/Java wrappers | |
Performance | Native (compiled Rust) | Interpreted | Native but heavy |
Dependencies | Zero | Varies | Poppler, Java, Ghostscript |
Memory safety | Rust ownership model | GC-dependent | Manual / GC |
Type stubs | Public API inventory + strict mypy checks on develop | Varies | Varies |
AI-ready (MCP) | Built-in | No | No |
MCP Server
Expose the supported PDF tools to your AI agent:
oxidize-mcpThe built-in Model Context Protocol server exposes 12 tools, 5 static resources plus a session resource template, and 5 prompts over stdio. Install oxidize-pdf[mcp] to include its dependencies; the base library keeps MCP optional.
Version 0.20 uses FastMCP 4 and MCP Python SDK 2, with support for both modern and legacy stdio clients. See the migration guide for versions and installation routes.
If tools/list returns an empty array with an older installation (issue #198), upgrade to 0.20.0 or newer. These versions fix tool registration when starting server.py directly. On Windows, use the same Python environment for installation and startup:
python -m pip install --upgrade "oxidize-pdf[mcp]"
python -m oxidize_pdf.mcp.serverFor a custom JSON-RPC client, send initialize, wait for its response, then send notifications/initialized before tools/list. Read newline-delimited JSON-RPC from stdout and keep stderr separate; the startup banner is written to stderr.
Claude Desktop integration
Add to your claude_desktop_config.json:
{
"mcpServers": {
"oxidize-pdf": {
"command": "oxidize-mcp",
"env": {
"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"
}
}
}
}GitHub Copilot (VS Code) integration
Copilot's agent mode speaks MCP. Add .vscode/mcp.json to your workspace:
{
"servers": {
"oxidize-pdf": {
"command": "oxidize-mcp",
"env": {
"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"
}
}
}
}Open the Chat view, switch to Agent mode, and the 12 PDF tools appear in
the tool picker. (The same block also works under the mcp.servers key in your
user settings.json if you prefer a global install.)
OpenAI Agents SDK integration
The OpenAI Agents SDK spawns the server over stdio and exposes its tools to an agent:
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async with MCPServerStdio(
params={"command": "oxidize-mcp", "env": {"OXIDIZE_WORKSPACE": "/path/to/your/pdfs"}},
cache_tools_list=True,
) as server:
agent = Agent(
name="PDF assistant",
instructions="Use the oxidize-pdf tools to inspect and manipulate PDFs.",
mcp_servers=[server],
)
result = await Runner.run(agent, "How many pages does report.pdf have?")
print(result.final_output)A runnable version is in examples/openai_agents_quickstart.py.
Both integrations run the server locally over stdio, so its tools operate on PDFs in the configured workspace directory. This package does not expose a hosted HTTP endpoint. ChatGPT web needs a remote connection; developer-mode testing can also use Secure MCP Tunnel with a local stdio server. No ChatGPT plugin is published by this migration.
Available tools
Tool | What it does |
| Read metadata — page count, version, encryption status, title, author |
| Extract text from all pages or a specific page |
| Convert to markdown, chunks, or RAG-optimized format |
| Create a new PDF with optional metadata |
| Save a session to disk, with optional encryption |
| Add pages, text, and graphics to a session |
| Add text annotations and highlights |
| Split, merge, rotate, extract pages, reverse, overlay |
| Create, fill, read, and validate form fields |
| Encrypt, check permissions, verify signatures |
| Extract structured entities from pages |
| Validate structure, detect corruption, check PDF/A compliance |
The server also exposes resources (session data, capabilities, version info) and prompts (guided workflows for summarization, data extraction, form filling, and more).
Configuration
OXIDIZE_WORKSPACE=/path/to/pdfs oxidize-mcpThe server is configured entirely through environment variables:
Variable | Default | Purpose |
|
| Sandbox root; all paths must resolve inside it. |
| (none) | Comma-separated extra directories allowed outside the workspace. |
|
| Reject input PDFs larger than this on disk. |
|
| Reject documents with more pages than this before any extraction work. |
|
| Cap the serialized size of a tool's JSON response (10 MB). |
|
| Maximum concurrent stateful PDF-creation sessions. |
|
| Cap the content a single session may accumulate (10 MB). |
|
| Session expiry, in seconds. |
Resource caps (OXIDIZE_MAX_*) protect the server from a large or malicious
PDF: oversized documents are rejected up front and tool responses are bounded
rather than serialized unbounded. Exceeding a cap returns an error with code
RESOURCE_LIMIT.
Or start programmatically:
from oxidize_pdf.mcp.server import run
run()Python API
Create a PDF
from oxidize_pdf import Document, Page, Font, Color
doc = Document()
doc.set_title("My Document")
doc.set_author("Jane Doe")
page = Page.a4()
page.set_font(Font.HELVETICA, 24.0)
page.set_text_color(Color.black())
page.text_at(72.0, 750.0, "Hello from oxidize-pdf!")
page.set_font(Font.TIMES_ROMAN, 12.0)
page.text_at(72.0, 700.0, "Generated with Python + Rust.")
doc.add_page(page)
doc.save("output.pdf")Parse an existing PDF
from oxidize_pdf import PdfReader
reader = PdfReader.open("document.pdf")
print(f"Pages: {reader.page_count}, Version: {reader.version}")
for i, text in enumerate(reader.extract_text()):
print(f"--- Page {i + 1} ---")
print(text)Inspect tagged PDF structure
from pathlib import Path
from oxidize_pdf import TaggedPdfLimits, TaggedPdfValidationOptions, validate_tagged_pdf
report = validate_tagged_pdf(
Path("input.pdf").read_bytes(),
TaggedPdfValidationOptions(TaggedPdfLimits(max_depth=64)),
)
for finding in report.findings:
print(finding.severity, finding.code, finding.path, finding.message)The report includes structure elements, artifacts, role/class maps, the parent
number tree and content MCIDs. Original dictionary values remain typed
PdfValue objects, including unknown attributes and reference generations.
Results are immutable snapshots; returned lists and dictionaries are owned copies.
valid means the core validator found no error-level findings. It is not a
PDF/UA certificate: an untagged PDF can have tagged=False, valid=True and a
MissingStructureTree warning. Malformed input raises PdfParseError, locked
input raises PdfEncryptionError, and exceeded validation budgets raise PdfError.
TaggedPdfLimits bounds visited objects, traversal depth, entries and decoded
content bytes (defaults: 100,000; 256; 1,000,000; 256 MiB). These are core validator
budgets, not a process memory or time limit: input copying and initial PDF parsing
occur before traversal. No PDF bytes are modified.
Operations
from oxidize_pdf import split_pdf, merge_pdfs, rotate_pdf, extract_pages
split_pdf("input.pdf", "output_dir/") # Split into individual pages
merge_pdfs(["part1.pdf", "part2.pdf"], "merged.pdf") # Merge multiple PDFs
rotate_pdf("input.pdf", "rotated.pdf", 90) # Rotate all pages
extract_pages("input.pdf", "subset.pdf", [0, 2, 4]) # Extract specific pagesGraphics
from oxidize_pdf import Document, Page, Color
doc = Document()
page = Page.a4()
page.set_fill_color(Color.hex("#3498db"))
page.draw_rect(72.0, 700.0, 200.0, 100.0)
page.fill()
page.set_stroke_color(Color.red())
page.set_line_width(2.0)
page.draw_circle(300.0, 500.0, 50.0)
page.stroke()
doc.add_page(page)
doc.save("graphics.pdf")Types
from oxidize_pdf import Color, Point, Rectangle, Margins, Font
# Colors
Color.rgb(1.0, 0.0, 0.0) # RGB
Color.hex("#ff6600") # Hex
Color.cmyk(0.0, 1.0, 1.0, 0.0) # CMYK
# Geometry
Point(72.0, 720.0)
Rectangle.from_xywh(72.0, 72.0, 468.0, 648.0)
Margins.uniform(72.0)
# Fonts — all 14 standard PDF fonts
Font.HELVETICA # Font.HELVETICA_BOLD
Font.TIMES_ROMAN # Font.TIMES_BOLD
Font.COURIER # Font.COURIER_BOLDError handling
from oxidize_pdf import PdfReader, PdfError, PdfIoError, PdfParseError
try:
reader = PdfReader.open("missing.pdf")
except PdfIoError as e:
print(f"I/O error: {e}")
except PdfParseError as e:
print(f"Parse error: {e}")
except PdfError as e:
print(f"PDF error: {e}")Exception hierarchy: PdfError > PdfIoError, PdfParseError, PdfEncryptionError, PdfPermissionError
MCP Server
oxidize-pdf includes an MCP server that exposes PDF capabilities to AI assistants like Claude. Install with the mcp extra:
pip install oxidize-pdf[mcp]Claude Desktop
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"oxidize-pdf": {
"command": "uvx",
"args": ["--from", "oxidize-pdf[mcp]", "oxidize-mcp"]
}
}
}Claude Code
claude mcp add oxidize-pdf -- uvx --from "oxidize-pdf[mcp]" oxidize-mcpAvailable tools
Tool | Description |
| Open a PDF and get metadata (pages, version, encryption) |
| Extract text content from PDF pages |
| Convert between PDF versions |
| Analyze structure, fonts, images, and compliance |
| Extract images and digital signatures |
| Split, merge, rotate, extract, and reorder pages |
| Add text annotations, highlights, and stamps |
| Create, fill, and read PDF form fields |
| Encrypt, decrypt, and set document permissions |
| Create a new PDF document with pages |
| Add text, shapes, and images to pages |
| Save the document to file or bytes |
Resources
oxidize://fonts— Available built-in PDF fontsoxidize://page-sizes— Standard page sizes with dimensionsoxidize://capabilities— Server capabilities and tool listingoxidize://version— Version informationoxidize://workspace— PDF files in the workspace directoryoxidize://session/{id}— Session data by ID
Known limitations
Encryption write support:
Document.encrypt()configures encryption parameters but the underlying Rust library does not yet serialize the encryption dictionary to the PDF output. Reading encrypted PDFs works correctly.Image extraction returns raw embedded streams:
extract_images_from_pdfextracts each embedded image as-is (e.g. aDCTDecodeJPEG is written byte-for-byte). Image preprocessing — auto rotation-correction, contrast enhancement, denoise, upscaling, force-grayscale — is not available, because the build excludes the upstreamexternal-imagesfeature (and itsimage-crate dependency). This keeps extraction faithful and lossless; it does not silently return empty or stub results.CPython only: PyPy and GraalPy are not supported.
License
MIT — see LICENSE for details.
Semantic comparison with revision history
from oxidize_pdf import (
compare_pdfs_semantically, SemanticComparisonLimits, SemanticComparisonOptions,
)
options = SemanticComparisonOptions(SemanticComparisonLimits(
max_objects=100_000, max_depth=256,
max_decoded_stream_bytes=256 * 1024 * 1024,
max_canonical_bytes=256 * 1024 * 1024,
max_extracted_text_bytes=64 * 1024 * 1024,
max_unreachable_objects=10_000, max_revisions=1024,
))
result = compare_pdfs_semantically(original_bytes, revised_bytes, options)
for difference in result.differences:
print(difference.path, difference.class_, difference.description)
for revision in result.right_revisions:
print(revision.index, revision.xref_offset, revision.semantic_fingerprint.hex())
for change in revision.object_changes:
print(change.object_number, change.generation, change.kind)The immutable result exposes both revision histories, unreachable object
identities and pairs of semantically equivalent objects. Collection properties
return independent snapshots. Revision fingerprints are 32-byte hashes, and
revisions are ordered oldest first. class_ is the Python spelling of Rust's
class field. Difference classes are Visual, Textual, Structural, Metadata,
Security and SerializationOnly. semantically_equal may be true when the only
difference is SerializationOnly; it includes supported historical semantics,
so restoring the latest state does not erase an intervening semantic change.
Limits apply independently to each document. Decoded/canonical budgets count
work across repeated traversals, not just unique input bytes. Initial input
copies, parsing and some extraction allocations precede these checks; the
options are not process-wide memory/time limits. Malformed input, exceeded
budgets and unsupported decoding raise PDF errors; encrypted input raises
PdfPermissionError and must be decrypted before comparison.
This API compares the core's supported object/text domains, not rendered pixels,
and does not certify signatures or irreversible redaction. Core 5.4.2 includes
physical ObjStm/XRef containers among objects unreachable from Root/Info. A
qpdf conversion to object streams can therefore report Structural differences
at /UnreachableObjects and /Revisions despite equal text and page content.
The binding preserves that conservative result; it does not promise equivalence
across every PDF layout. The existing compare_pdfs API remains available.
Images from pixel buffers
from oxidize_pdf import Image, ImageColorSpace
rgb = Image.from_raw_data(b'\xff\x00\x00', 1, 1, ImageColorSpace.DeviceRGB)
gray = Image.from_gray_data(b'\x80', 1, 1)
rgba = Image.from_rgba_data(b'\xff\x00\x00\x80', 1, 1)
assert rgba.alpha_data == b'\x80'
assert rgba.soft_mask.data == b'\x80'from_raw_data(data, width, height, color_space, bits_per_component=8) accepts
DeviceGray, DeviceRGB and DeviceCMYK. Components are packed in pixel order, most
significant bits first, with each scanline padded to a whole byte. Supported
component depths are 1, 2, 4, 8 and 16; 16-bit components use big-endian byte
order. RGBA and grayscale convenience constructors use 8-bit components.
All three constructors require positive dimensions and an exact buffer length.
Invalid sizes/depths raise ValueError; values outside unsigned argument ranges
raise OverflowError. The RGBA/gray constructors reject products exceeding
u32::MAX before calling the published core's dimension arithmetic. Validation
happens before copying the pixel buffer.
data, format, bits_per_component, alpha_data and soft_mask expose owned
snapshots. ImageFormat distinguishes Jpeg, Png, Tiff and Raw. Data is the core's
image payload: JPEG remains encoded, while PNG/TIFF constructors expose decoded
pixel data. RGBA separates RGB data from alpha; its soft mask can be inspected
independently. These APIs use the existing core without external-images or
new dependencies. Explicit mask creation/application and writer-object export
remain outside this increment.
Marked content on new pages
Page.begin_marked_content(tag) and
Page.begin_marked_content_with_actual_text(tag, actual_text) return a page-local
MCID. Pair every begin with Page.end_marked_content(); next_mcid() and
marked_content_depth() inspect the state. Tags are nonempty literal names
(UTF-8 bytes are PDF-name escaped, so pass "Span", not "/Span"). Empty tags
raise ValueError without changing state; ending an empty stack raises PdfError.
page = Page.a4()
page.set_font(Font.HELVETICA, 12)
mcid = page.begin_marked_content_with_actual_text("Span", "café 😀")
page.text_at(40, 700, "visible glyphs")
page.end_marked_content()ActualText is a Unicode logical replacement; copying/extraction depends on the
viewer honoring it. For tagged structure, attach the returned ID using
StructureElement.add_mcid(page_index, mcid) and install the tree with
Document.set_struct_tree(tree). These operations alone do not certify PDF/UA.
The core writes these markers in the text context; they are intended for text
operations, not as a general scope around separately buffered graphics.
Coordinate transformations
TransformMatrix(a, b, c, d, e, f) exposes immutable affine components,
IDENTITY, translate, scale, rotate (radians), flip_y, multiply,
transform_point(Point) and to_pdf_ctm(). Composition a.multiply(b) applies
b first, then a. Values retain the core's floating-point semantics, including
singular and nonfinite matrices; validate them before using the CTM in a PDF.
from oxidize_pdf import CoordinateSystem, Point, TransformMatrix
matrix = TransformMatrix.translate(10, 20).multiply(TransformMatrix.scale(2, 3))
point = matrix.transform_point(Point(4, 5)) # Point(18, 35)
custom = CoordinateSystem.custom(matrix)
pdf_point = custom.to_pdf_standard(Point(4, 5), 800) # Point(18, 35)CoordinateSystem supports point, Y-only and matrix conversions to PDF
coordinates, plus grows_upward(). For custom systems, Y-only conversion uses
x=0, orientation tests d > 0, and page height is ignored. Page.coordinate_system
returns a snapshot of the stored system. In core 5.4.2, setting this property
through set_coordinate_system does not automatically transform page content;
convert positions explicitly. LayoutManager remains outside this binding increment.
RenderContext(coordinate_system, page_width, page_height) stores mutable
coordinate_system, page_width, page_height and current_transform fields.
Its to_pdf_standard(Point) and y_to_pdf(y) methods use the current system and
height. The matrix is initialized from the constructor's system and height;
later field assignments do not recalculate it, and conversions do not read it.
This preserves core 5.4.2 behavior. Getters return independent value snapshots.
Use context.current_transform.transform_point(point) when you explicitly want
to apply the stored matrix. Numeric fields retain the core's f64 semantics.
Page.content_width and Page.content_height subtract the page margins.
Page.content_area returns (left, bottom, right, top) bounds in unrotated PDF
coordinates, rather than a position and size. These read-only properties reflect
current margins and can be negative or inverted when margins exceed page size.
Page.effective_width and Page.effective_height reflect rotation: 90°/270° swap
width and height. They use the full page dimensions, without subtracting margins.
Setting a coordinate system does not change these geometry properties.
Custom clipping paths
from oxidize_pdf import ClippingPath, WindingRule
clip = (ClippingPath().add_rect(10, 10, 80, 80)
.add_polygon([(30, 30), (70, 30), (50, 70)])
.with_winding_rule(WindingRule.EvenOdd))
page.set_clipping_path(clip)move_to, line_to, curve_to, close_path and add_rect, add_circle,
add_ellipse, add_rounded_rect, add_polygon mutate and return the same path.
with_winding_rule returns an independent copy; the default is NonZero.
clear() removes commands while preserving the rule. is_empty reports whether
commands exist. Page.set_clipping_path retains a copy.
commands returns owned ClippingPathCommand snapshots with read-only kind
and copied coordinates: move/line use [x, y], cubic curves use two control
points and an endpoint [x1, y1, x2, y2, x3, y3], rectangles use
[x, y, width, height], and close has no coordinates. Coordinates are PDF user
space values; callers supply valid finite geometry. to_pdf_operations() emits
the core's clipping operators, including W/W* and n.
The existing rect, circle, and ellipse factories remain available.
This API does not expose path intersection, text clipping, or a clipping stack.
Line dash patterns
LineDashPattern.dashed(dash_length, gap_length) and
LineDashPattern.dotted(dot_size, gap_size) create two-length patterns with zero
phase. Both use the same PDF dash representation; dots depend on length and line
cap, and the factory does not select a round cap. Pass a pattern to
page.set_dash_pattern(pattern); use solid() to reset to a continuous line.
array returns a copied list and phase is read-only. to_pdf_string() returns
the core representation (without the d operator), rounded to two decimals.
The core serializes non-finite components as zero and an empty array as [] 0,
regardless of stored phase. Negative values remain unchanged; this API does not
validate whether an arbitrary pattern is legal PDF. Use nonnegative lengths,
with at least one positive length for a nonempty pattern, and a nonnegative phase.
Blend modes
All 16 modes from the published core 5.4.2 are available through BlendMode.
Alongside the existing modes, use DIFFERENCE, EXCLUSION, HUE,
SATURATION, COLOR, or LUMINOSITY with page.set_blend_mode(mode) before
drawing. mode.pdf_name() returns the PDF name (for example, "Color"), and
repr(mode) uses the Python constant name. These immutable values configure
the page's extended graphics state; use BlendMode.NORMAL to reset blending.
Color conversion and inspection
color.to_rgb() and color.to_cmyk() return independent Color values.
cmyk_components() returns four converted components, including for RGB/Gray
inputs. Existing c, m, y, k properties still return zero for non-CMYK
colors. is_rgb(), is_cmyk(), is_gray() inspect the stored variant, and
color_space_name() returns DeviceRGB, DeviceCMYK, or DeviceGray.
The existing color_space property keeps its shorter names.
Conversions use the published core 5.5.0's approximate formulas, without ICC
profiles or calibrated color management. Gray becomes equal RGB components or
CMYK with only black ink. RGB black converts to (0, 0, 0, 1). Constructors
clamp finite out-of-range values and infinities; NaN may propagate through
conversion. Callers should supply finite components for predictable colors.
Streaming and lazy document options
Core 5.5.0 exposes two separate consumers with immutable options and configuration
snapshots. Existing calls such as LazyDocument.open(path) remain valid.
from oxidize_pdf import LazyDocument, MemoryOptions, StreamingDocument, StreamingOptions
options = StreamingOptions(page_cache_size=2, memory_limit=2 * 1024 * 1024,
max_content_stream_size=1024 * 1024)
with StreamingDocument.open("input.pdf", options) as document:
configuration = document.configuration()
while (page := document.next_page()) is not None:
print(page.number, page.extract_text_streaming())
print(document.cached_pages(), document.memory_usage())
document.clear_cache()
with LazyDocument.open("input.pdf", MemoryOptions(cache_size=20)) as document:
page = document.get_page(0)
print(page.width, document.memory_stats().cached_objects)
print(document.configuration().unsupported)StreamingOptions accepts buffer_size, page_cache_size,
max_content_stream_size, progressive_hints, and memory_limit as keyword
arguments. Its minimal_memory() and fast_processing() presets and
with_buffer_size(), with_page_cache_size(), and with_memory_limit() builders
return independent values. MemoryOptions accepts lazy_loading,
memory_mapping, cache_size, streaming, buffer_size, and mmap_threshold;
use small_file()/large_file() or its with_lazy_loading(),
with_memory_mapping(), with_cache_size(), and with_streaming() builders.
All fields are readable properties.
Consumer | Effective contract |
|
|
|
|
configuration() returns owned requested and effective snapshots. A zero
buffer is normalized to one byte. Streaming progressive hints are reserved:
effective.progressive_hints is false and progressive_hints_unsupported reports
a requested hint. Lazy memory mapping and streaming are unsupported in this
adapter: unsupported lists UnsupportedMemoryOption.MemoryMapping and/or
.Streaming, effective flags are false, and the effective mapping threshold is
the platform's maximum usize. These flags do not enable those modes.
These limits do not cap total RAM, parser caches, extraction working memory,
caller-retained values, or peak allocations. Lazy memory_stats() reports
adapter cache hits, misses, and retained object count; allocated_bytes,
lazy_loads, and mapped_regions are uninstrumented counters, not measurements
of process memory. clear_cache() clears adapter retention, not caller values.
Native StreamingDocument pages use unrotated MediaBox geometry. The existing
PageStreamer retains its rotated geometry and seek behavior; its options belong
to a different API. LazyDocument.get_page() returns ParsedPage with effective
rotation. Pages and snapshots remain usable after closing either document;
operations requiring the closed document raise ValueError. Both consumers
reject encrypted documents; unlock through the reader API where needed.
Incremental tagged structure editing
IncrementalTaggedPdfEditor(pdf) owns an immutable byte snapshot.
TaggedPdfMutation supports create_element, set_element_attribute (omit the
value to remove it), reparent_element, associate_mcid, and
set_parent_tree_entry. Inputs use TaggedPdfObjectRef (number and generation)
and lossless PdfValue objects. plan(mutations) returns effective mutations and
changed object identities; apply(mutations) returns pdf_bytes, the plan, and
validation reports before and after the revision. Both evaluate mutations in
order. A change followed by a restoration retains both effective operations.
from oxidize_pdf import IncrementalTaggedPdfEditor, TaggedPdfMutation, TaggedPdfObjectRef, PdfValue
editor = IncrementalTaggedPdfEditor(source_bytes)
mutation = TaggedPdfMutation.set_element_attribute(
TaggedPdfObjectRef(6, 7), "T", PdfValue.string(b"New title"))
result = editor.apply([mutation])
assert result.pdf_bytes.startswith(source_bytes)Object references must identify structure elements in the actual source PDF. Editors and returned snapshots are independent; a failed batch leaves the editor unchanged. Empty/no-op ordinary edits preserve source bytes exactly. Applied revisions preserve the exact source prefix, including for xref-stream sources.
with_validation_options() accepts TaggedPdfValidationOptions and its limits.
preflight() performs unambiguous derived-index recovery for inspection;
it does not prepare or mutate the editor. with_recovered_indexes() prepares an
independent editor with recovered indexes/page keys. with_document_language()
supplies a missing language; it rejects blank values and existing nonempty
languages. Recovery does not invent accessibility descriptions or language.
Preparation builders are replayed in order for each operation.
preparation_objects() inventories their changes, including xref-stream objects,
separately from plan().changed_objects. Prepared editors return bytes only when
the full result validates, including empty mutation batches. Missing descriptions
must be supplied explicitly; preparation refuses to overwrite existing ones.
Ordinary editors return diagnostic reports and may return structurally invalid
results. Reports do not certify PDF/UA conformance.
Encrypted inputs and DocMDP P1/P2/P3 restrictions are enforced by the core in planning, application and preparation. Validation limits bound the validation work they describe, not all parsing, copying or preparation work. No signature verification, OCR recognition, or global memory cap is implied by this editor.
Incremental text notes and highlights
IncrementalTextNoteEditor(pdf) reads notes() and applies batches of
TextNoteMutation.add(page_index, position, contents), .update(id, position, contents), and .remove(id). position is an (x, y) pair in PDF coordinates.
New notes have a 20 × 20 point rectangle; updates preserve the existing rectangle's
width, height and other dictionary keys. Contents must contain non-whitespace
text and support Unicode. TextNoteUpdate.added_notes contains only newly added
notes, in request order; reread the result to obtain the complete note set.
IncrementalHighlightEditor(pdf) reads highlights() and accepts
HighlightMutation.add(page_index, quadrilaterals, color, opacity=None, contents=None) and .remove(id). HighlightUpdate.highlights contains the
complete current set, ordered by page and identity. The native API has no
highlight update operation.
from oxidize_pdf import (
IncrementalTextNoteEditor, TextNoteMutation,
IncrementalHighlightEditor, HighlightMutation,
HighlightQuad, HighlightColor, HighlightOpacity,
)
notes = IncrementalTextNoteEditor(source_bytes).apply([
TextNoteMutation.add(0, (40., 50.), "Revisar: café 世界"),
])
quad = HighlightQuad([(40., 90.), (100., 90.), (40., 70.), (100., 70.)])
result = IncrementalHighlightEditor(notes.pdf_bytes).apply([
HighlightMutation.add(0, [quad], HighlightColor([1., 1., 0.]),
HighlightOpacity(.5), "Revisar"),
])
output_bytes = result.pdf_bytesHighlightQuad takes exactly four (x, y) pairs in PDF QuadPoints order: start
and end of one edge, followed by start and end of the opposite edge. The core
rejects non-finite, repeated, crossed, concave and degenerate geometry.
HighlightColor accepts exactly three finite DeviceRGB components in [0, 1];
HighlightOpacity accepts a finite value in [0, 1]. Their properties return
owned values. A highlight requires at least one quad. Optional contents preserve
None versus an empty string; opacity omission differs from explicit zero.
Page indices are zero-based. Coordinates must fit the page's effective CropBox
(or MediaBox if absent) in unrotated PDF space; inherited bounds are respected.
Identities are TextNoteId/HighlightId with object and generation numbers.
Targeting another subtype, a nonexistent identity or one target twice in a batch
raises a PDF error. Direct target annotation dictionaries and annotations shared
across pages are unsupported because they lack an unambiguous stable identity.
Editors own immutable source snapshots. Results preserve the source as an exact byte prefix, with one appended revision for a successful nonempty batch; failed batches leave the source/editor unchanged. Empty batches preserve bytes exactly. Notes, highlights, mutations and result snapshots expose read-only properties. These APIs append revisions in memory; removal detaches references, so historical annotation bytes remain in the file. They do not provide irreversible redaction.
Core 5.5.0 enforces DocMDP for nonempty batches: P1/P2 reject edits and P3 permits these annotation operations. Reads and empty batches remain available on those certified inputs; encrypted inputs are rejected. This permission check does not verify signature authenticity. These editors preserve or emit native annotation dictionaries; they do not promise a generated appearance stream or identical annotation rendering in every viewer, nor impose a global memory limit.
Available Tools
12 toolsadd_pdf_contentAdd content to a PDF sessionA
Append text or a new page to an open create_pdf session (step 2 of 3).
Mutates the in-memory session; nothing is written to disk until save_pdf. Returns JSON {status, session_id, page_count} on success, or {error, code} if the session is missing/inactive or required text fields are absent. Coordinates use PDF points with the origin at the bottom-left of the page.
Call repeatedly to build up pages, then call save_pdf. This only works on a session from create_pdf — to add notes/highlights to an existing PDF file use annotate_pdf instead.
| Name | Required | Description | Default |
|---|---|---|---|
| x | No | Horizontal position in PDF points from the left edge. Required when content_type='text'. | |
| y | No | Vertical position in PDF points from the bottom edge (origin is bottom-left). Required when content_type='text'. | |
| font | No | Font name (e.g. 'Helvetica', 'Courier', 'Times-Roman'). Defaults to Helvetica when omitted. | |
| content | No | Text to draw. Required when content_type='text'. | |
| font_size | No | Font size in points for text content. | |
| session_id | Yes | Session id returned by create_pdf. Must be active. | |
| content_type | Yes | 'text' draws a text string at (x, y) on the current page; 'new_page' appends a blank page and makes it current. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses in-memory mutation, no disk write until save_pdf, return formats, coordinate system, and prerequisites. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three paragraphs with front-loaded purpose, efficient behavioral details, and usage guidelines. No redundant sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers preconditions, mutations, errors, return format, and alternatives. With output schema present, return description is sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. Description adds some value (e.g., coordinate origin) but mostly repeats schema info.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'append' and resource 'open create_pdf session' and positions it as step 2 of 3, distinguishing it from sibling tools like annotate_pdf.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use (after create_pdf, before save_pdf) and when not (use annotate_pdf for existing files). Provides clear workflow context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
analyze_pdfAnalyze / validate a PDFARead-onlyIdempotent
Inspect a PDF's structural health or conformance (does not read content).
Returns JSON keyed by the chosen check: validate → {valid, error_count, warning_count}; corruption → {corrupted, corruption_type, severity, found_pages, file_size, errors}; compliance → {level, is_valid, error_count, warning_count, compliance_percentage}; compare → {structurally_equivalent, content_equivalent, similarity_score, difference_count}. Read-only.
Use this to verify a file is well-formed, archival-grade, or identical to another. To read titles/author/page counts use read_pdf; for the text use extract_text.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to the PDF file to analyze, relative to the workspace. | |
| check | No | Which analysis to run: 'validate' = structural validity with error/warning counts; 'corruption' = damage severity and type; 'compliance' = PDF/A conformance at compliance_level; 'compare' = diff against compare_path. | validate |
| compare_path | No | Second PDF to diff against. Required when check='compare', ignored otherwise. | |
| compliance_level | No | PDF/A conformance level to test. Used only when check='compliance'. Letter = conformance class (a/b/u), number = PDF/A part (1/2/3). | a1b |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint and idempotentHint. Description adds that it is read-only and details return JSON structure for each check, going beyond annotations. No contradictions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two paragraphs, well-organized. First paragraph describes output, second provides usage guidance. No fluff or redundancy. Could be slightly more concise but still efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (4 parameters, 2 enums, output schema), the description thoroughly covers behavior, return structure, and use cases. Annotations and schema further enrich completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. Description adds context by explaining each check value and when compare_path is required. Provides meaning beyond schema enums.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool inspects structural health or conformance, not content. It distinguishes from siblings like read_pdf and extract_text by specifying that it does not read content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use: verify well-formed, archival-grade, or identical. Also provides exclusions: use read_pdf for titles/author/page counts and extract_text for text. Clear alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
annotate_pdfAnnotate a PDFADestructive
Stamp a sticky note or highlight onto a page of an existing PDF.
Writes the annotated copy to output_path (overwriting any existing file) and returns JSON {status, annotation_type}; out-of-range pages or coordinates outside the page bounds return {error, code}. Coordinates are in PDF points with the origin at the bottom-left.
Use this to mark up a document. To reorder/rotate/overlay whole pages use manipulate_pdf; to author a new PDF use create_pdf.
| Name | Required | Description | Default |
|---|---|---|---|
| x | Yes | Horizontal anchor in PDF points from the left edge. | |
| y | Yes | Vertical anchor in PDF points from the bottom edge (origin is bottom-left). | |
| page | Yes | 0-based index of the page to annotate. | |
| width | No | Highlight width in points. Used only for 'highlight'. | |
| height | No | Highlight height in points. Used only for 'highlight'. | |
| contents | No | Note text for a 'text' annotation. Ignored for 'highlight'. | |
| input_path | Yes | Source PDF to annotate, relative to the workspace. | |
| output_path | Yes | Destination .pdf path; overwritten if it already exists. | |
| annotation_type | Yes | 'text' adds a sticky-note marker at (x, y); 'highlight' draws a highlight rectangle of width×height anchored at (x, y). |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate destructiveHint=true and readOnlyHint=false. The description adds that it overwrites the output file, returns JSON with status/error for out-of-range pages or coordinates, and explains the coordinate system. This provides useful context beyond annotations, though it does not detail potential side effects like file locking.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences plus a brief usage note, conveying all essential information without redundancy. It front-loads the core action and follows with behavior and alternatives, making it efficient and easy to process.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the detailed input schema and annotations, the description covers all necessary context: purpose, behavior, error handling, coordinate system, and alternatives. It is sufficiently complete for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema has 100% coverage, but the description adds value by explaining the return format ({status, annotation_type} or {error, code}) and clarifying that width/height only apply to 'highlight'. This goes beyond the schema descriptions, compensating for the high coverage baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool stamps a sticky note or highlight onto an existing PDF page, with specific verb 'stamp' and resource 'page of an existing PDF'. It distinguishes from sibling tools by mentioning manipulate_pdf for page reordering and create_pdf for authoring new PDFs, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use: 'Use this to mark up a document.' It provides clear alternatives: 'To reorder/rotate/overlay whole pages use manipulate_pdf; to author a new PDF use create_pdf.' It also notes that output_path is overwritten, guiding correct invocation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
convert_pdfConvert PDF to text representationARead-onlyIdempotent
Convert a whole PDF into a text representation for downstream LLM use.
Returns JSON: {content, format} for 'markdown', or {chunks, format} for 'chunks'/'rag' (each chunk carries its index and page_numbers; rag chunks add token_estimate and heading_context). Read-only.
Use this when you need structure or chunking. If you just want the raw reading text use extract_text; for per-run coordinates use extract_entities.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to the PDF file, relative to the configured workspace. | |
| format | Yes | Output representation: 'markdown' = one structured Markdown document; 'chunks' = fixed-size token windows; 'rag' = heading-aware semantic chunks for retrieval pipelines. | |
| overlap | No | Token overlap carried between consecutive chunks. Applies to format='chunks' only; ignored for 'markdown' and 'rag'. | |
| password | No | User password to unlock an encrypted PDF before conversion. | |
| max_tokens | No | Target maximum tokens per chunk. Applies to format='chunks' only; ignored for 'markdown' and 'rag' (rag uses heading-aware semantic chunking with a fixed internal budget). |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint. The description adds detailed return structure per format (JSON with content/format or chunks with index/page_numbers/heading_context), and confirms read-only nature, exceeding what annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise: two paragraphs. First paragraph covers purpose and return format. Second paragraph provides usage guidelines. No unnecessary words, front-loaded with key information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 5 parameters (2 required), output schema present, and sibling tools, the description covers all essential aspects: purpose, return format, usage guidance, and behavioral notes (read-only). It is complete and useful for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so each parameter is documented. The description does not add additional parameter-level semantics beyond the schema, but it explains how parameter choices affect output format (e.g., overlap applies only to chunks). This is adequate for the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool converts a whole PDF to a text representation for LLM use, specifying verb (convert) and resource (PDF). It distinguishes from siblings like extract_text and extract_entities by mentioning alternative use cases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use this tool ('when you need structure or chunking') and when to use alternatives ('for raw reading text use extract_text; for per-run coordinates use extract_entities'). Provides clear decision guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_pdfStart a PDF creation sessionA
Open an in-memory PDF building session; the first step of authoring a PDF.
Returns JSON {session_id, status, page_size}. No file is written here — this only allocates a session (with one blank starting page) held in server memory and subject to a TTL. Not idempotent: each call creates a new session.
Workflow: create_pdf → add_pdf_content (text / new pages, repeatable) → save_pdf (writes the file and closes the session). To annotate or fill an existing PDF instead of authoring one, use annotate_pdf or manage_forms.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Document title; stored in the PDF metadata on save. | |
| author | No | Document author; stored in the PDF metadata on save. | |
| page_size | No | Page size for every page in this document. | a4 |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds significant context beyond annotations: returns session_id/status/page_size, no file written, session in memory with TTL, not idempotent. Annotations already indicate no idempotency, but description adds specific behavioral detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Efficient and well-structured: first sentence states purpose, then return info, then workflow and alternatives. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, workflow, return values, behavioral notes (TTL, idempotency), and alternatives. Adequate for a simple session creation tool with good schema and output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema has 100% description coverage for all three parameters, so description adds minimal extra meaning. Schema already defines title, author, page_size clearly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it opens an in-memory PDF building session as the first step of authoring a PDF. Distinguishes from siblings by outlining the workflow and explicitly mentioning alternatives for annotating or filling existing PDFs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit workflow (create_pdf → add_pdf_content → save_pdf) and warns against using it for annotating/filling existing PDFs, directing to annotate_pdf or manage_forms.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
extract_entitiesExtract positioned text runsARead-onlyIdempotent
Extract every text run of a PDF together with its layout geometry.
Returns JSON {path, entities, entity_count, page_count} where each entity is {text, page (0-based), x, y, font_size, font_name}. Coordinates are in PDF points with the origin at the bottom-left of the page. Read-only.
Use this for layout-aware tasks (table reconstruction, positional lookup, locating a label on the page). If you only need the reading text without coordinates, use extract_text; for Markdown or RAG chunks use convert_pdf.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to the PDF file, relative to the configured workspace. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint=true and idempotentHint=true. The description adds valuable behavioral context: output structure (JSON with fields), coordinate system (PDF points, bottom-left origin), and explicitly states 'Read-only.' No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences: first sentence states purpose, second explains output, third gives usage guidelines. It is concise, front-loaded with key information, and every sentence serves a purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description explains the output structure in detail (JSON with path, entities, entity_count, page_count, and entity fields). Output schema exists but description covers it sufficiently. For a 1-parameter tool, all necessary context is provided.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with a clear parameter description in the schema itself. The tool description does not repeat or add significant value beyond the schema's definition of the 'path' parameter. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Extract every text run of a PDF together with its layout geometry.' It distinguishes from siblings by explicitly naming alternatives: 'use extract_text' for reading text without coordinates, and 'use convert_pdf' for Markdown or RAG chunks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage context: 'Use this for layout-aware tasks...' and clearly states when not to use it by specifying alternatives, e.g., 'If you only need the reading text without coordinates, use extract_text; for Markdown or RAG chunks use convert_pdf.'
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
extract_textExtract plain textARead-onlyIdempotent
Extract the raw, unformatted text of a PDF as a single string.
Returns JSON {text, page_count} (plus page when a specific page was
requested). Read-only.
Use this when you want the plain reading text. If you need Markdown structure or chunking for LLM/RAG pipelines use convert_pdf; if you need each text run with its on-page coordinates and font use extract_entities.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | 0-based page index to extract. Omit to extract every page joined by newlines. Out-of-range indices return an error. | |
| path | Yes | Path to the PDF file, relative to the configured workspace. | |
| password | No | User password to unlock an encrypted PDF before extraction. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even though annotations already mark readOnlyHint=true and idempotentHint=true, the description adds further behavioral context. It states the tool is 'read-only' and describes the return JSON structure ({text, page_count} and 'page' when a specific page is requested). It also mentions that out-of-range page indices return an error. This goes beyond the annotations without contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is exceptionally concise: three sentences. The first sentence states purpose, the second describes return format and readonly nature, and the third provides usage alternatives. No filler or redundant information. Every sentence earns its place, and the structure is front-loaded with the core action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (3 parameters, 1 required, simple output schema), the description is complete. It covers return format, error behavior (out-of-range page), and usage context vs. siblings. The output schema exists but the description still explains return fields. All necessary information for correct invocation is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds some extra context beyond the schema: it mentions that 'page' parameter accepts a 0-based index and that out-of-range values return an error (already partly in schema but reinforced). It also implies that 'password' is for encrypted PDFs. However, it does not detail each parameter extensively; the schema already does that well. The added value justifies one point above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Extract the raw, unformatted text of a PDF as a single string.' It identifies the verb (extract), resource (raw text of PDF), and output format (a single string in JSON). The title 'Extract plain text' reinforces this. It also distinguishes from siblings by specifying when to use convert_pdf (for Markdown structure) and extract_entities (for coordinates and font). This is specific and differentiates among 12 sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Use this when you want the plain reading text.' and provides clear alternatives: 'if you need Markdown structure or chunking for LLM/RAG pipelines use convert_pdf; if you need each text run with its on-page coordinates and font use extract_entities.' This gives concrete when-to-use and when-not-to-use guidance, making it easy for the agent to select the correct tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_formsManage PDF form fieldsADestructive
Create, fill, read or validate PDF form fields.
Returns JSON per operation: create→{status, fields_created}; fill→{status, fields_filled}; read→{path, fields, page_count}; validate→{valid, fields}. 'create'/'fill' write output_path (overwriting); 'read'/'validate' are read-only computations.
Honest limitations: 'fill' lays the values into a new overlay at computed positions rather than mapping them onto the original AcroForm widgets; 'read' returns the page's text runs (not declared AcroForm field objects); 'validate' currently enforces only a non-empty (required) rule per value. For page-structure edits use manipulate_pdf; to read prose use extract_text.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Field definitions for 'create'. Each: {name, type:'text', x, y, width, height (points), default_value?}. | |
| values | No | Map of field name to value. Required for 'fill' and 'validate'. | |
| operation | Yes | 'create' a new PDF with text fields; 'fill' values onto a copy of an existing PDF; 'read' the text content of a form; 'validate' supplied values. 'read' and 'validate' do not write a file. | |
| input_path | No | Source PDF. Required for 'fill', 'read' and 'validate'; unused for 'create'. | |
| output_path | No | Destination .pdf path (overwritten if present). Required for 'create' and 'fill'; unused for 'read'/'validate'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description honestly discloses limitations: 'fill' creates a new overlay rather than mapping onto original AcroForm widgets, 'read' returns text runs not declared AcroForm field objects, and 'validate' only enforces non-empty rule. Annotations already indicate destructiveHint=true, but the description adds valuable context beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with a clear overview, return types listed by operation, behavioral details, limitations, and sibling references. Every sentence is informative and earns its place. It is concise yet complete.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (4 operations, 5 parameters, output schema, annotations), the description covers all essential aspects: return values per operation, side effects (overwriting), limitations, and usage context. No gaps remain for an AI agent to misuse the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. However, the description adds meaningful context: it explains which parameters are required per operation and provides detailed format for 'fields' (e.g., {name, type:'text', x, y, width, height, default_value?}). This goes beyond the schema's minimal descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states 'Create, fill, read or validate PDF form fields,' clearly identifying the verb (multiple operations) and resource (PDF form fields). It distinguishes from sibling tools like manipulate_pdf and extract_text in the last sentence.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance on when to use which operation (e.g., 'For page-structure edits use manipulate_pdf; to read prose use extract_text') and notes that 'create'/'fill' write and 'read'/'validate' are read-only. It also explains required parameters for each operation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manipulate_pdfRestructure PDF pagesADestructive
Restructure the pages of existing PDF file(s) and write a new PDF.
Each operation writes to output_path (overwriting any existing file) and returns JSON {status, operation}; on a missing required argument it returns {error, code}. Per-operation requirements: merge→input_paths; rotate→degrees; extract_pages→page_indices; overlay→overlay_path; split→output_path is a directory. Page indices are 0-based.
Use this for page-level structure. To stamp notes/highlights use annotate_pdf; to fill form fields use manage_forms; to encrypt use secure_pdf.
| Name | Required | Description | Default |
|---|---|---|---|
| degrees | No | Clockwise rotation in degrees (e.g. 90, 180, 270). Required for 'rotate'. | |
| operation | Yes | Page operation: 'split' one PDF into per-page files; 'merge' several PDFs into one; 'rotate' all pages; 'extract_pages' a subset; 'reverse' page order; 'overlay' one PDF on top of another. | |
| input_path | No | Source PDF. Required for every operation except 'merge' (which uses input_paths). | |
| input_paths | No | Ordered list of PDFs to combine. Required for 'merge'. | |
| output_path | No | Output location, overwritten if it exists. For 'split' this is an existing directory; for all other operations a .pdf file. | |
| overlay_path | No | PDF stamped on top of input_path. Required for 'overlay'. | |
| page_indices | No | 0-based page indices to keep, in order. Required for 'extract_pages'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations indicate destructiveHint=true and readOnlyHint=false. The description reinforces this by stating it 'overwrites any existing file' and describes return JSON for success and error cases. It adds specific behavioral details beyond annotations, such as the return format and overwrite behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise (6 sentences), well-structured, and front-loaded with purpose. Each sentence earns its place: purpose, return format, per-operation requirements, sibling links. No fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (6 operations, 7 parameters), the description covers all operations and required parameters. It mentions return format, and with an output schema present (context signals), further details are not needed. It is fully adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter already has a description. The tool description adds value by grouping parameters per operation in a compact summary, which aids quick understanding of dependencies. This goes beyond the schema by providing operational context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that the tool restructures PDF pages and writes new PDFs, listing six specific operations. It distinguishes itself from siblings by explicitly saying 'Use this for page-level structure' and pointing to alternatives for other tasks (annotate_pdf, manage_forms, secure_pdf).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use guidance ('page-level structure') and when-not-to-use with sibling references. It also lists per-operation required parameters (e.g., 'merge→input_paths'), helping the agent decide which parameters are needed for each operation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_pdfRead PDF metadataARead-onlyIdempotent
Read a single PDF's document-level metadata without parsing its content.
Returns a JSON object with: page_count, is_encrypted, version, title,
author, subject, keywords, and (when include_page_details=true) a pages
array of {index, width, height, rotation}. Read-only: never modifies the
file.
Use this to inspect what a PDF is before deciding how to process it. For structural validation, corruption/PDF-A checks, or comparing two files use analyze_pdf instead; for the actual text use extract_text. Encrypted files without a password return {is_encrypted, locked, message} rather than metadata.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to the PDF file, relative to the configured workspace. | |
| password | No | User password to unlock an encrypted PDF. Omit for unencrypted files; if omitted on an encrypted file the tool reports it as locked instead of failing. | |
| include_page_details | No | When true, also return per-page width, height (in PDF points) and rotation. Off by default to keep the response small. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint; description reinforces read-only nature, details return object structure, and explains behavior for encrypted files without password. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Five sentences with clear structure: purpose, return format, read-only emphasis, use cases with alternatives, and edge case handling. No unnecessary words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers tool purpose, return data, read-only behavior, usage guidance with alternatives, and edge cases. Output schema exists; description complements it fully. No gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but description adds context: explains include_page_details reduces response size and how password parameter affects output for encrypted files. Adds value beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states 'Read a single PDF's document-level metadata' with specific verb and resource, distinguishes from sibling tools like analyze_pdf and extract_text, and lists exact return fields.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use ('inspect what a PDF is before deciding how to process it') and when not to (use analyze_pdf for structural validation, extract_text for actual text). Also covers edge case of encrypted files.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
save_pdfSave and close a PDF sessionADestructive
Render an open create_pdf session to a PDF file (step 3 of 3, terminal).
Builds a Document from the session's accumulated pages, writes it to output_path (overwriting any existing file), then deletes the session — so the session_id is no longer usable afterwards. Returns JSON {status, path, page_count}, or {error, code} if the session is missing.
Only finalizes sessions created via create_pdf/add_pdf_content. To encrypt an already-saved PDF use secure_pdf; to add annotations use annotate_pdf.
| Name | Required | Description | Default |
|---|---|---|---|
| session_id | Yes | Session id returned by create_pdf to finalize. | |
| output_path | Yes | Destination .pdf path inside the workspace. An existing file at this path is overwritten. | |
| user_password | No | If set together with owner_password, the saved PDF is encrypted; this is the password required to open it. | |
| owner_password | No | Owner/permissions password. Encryption is applied only when both user_password and owner_password are provided. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses key behaviors beyond annotations: overwrites existing file, deletes the session making session_id unusable, and returns JSON {status, path, page_count} or error. Annotations already indicate destructiveHint=true, but the description adds specific details. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is five well-structured sentences. It front-loads the core action, then explains behavior, return format, constraints, and alternatives. No redundant or extraneous information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and available annotations, the description covers all essential aspects: purpose, lifecycle (step 3 of 3), behavior, return values, error handling, prerequisites, and alternatives. It is complete for an agent to understand when and how to invoke the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description adds contextual meaning: explains that session_id must come from create_pdf/add_pdf_content, that output_path can overwrite, and that encryption only applies when both passwords are provided. This adds value beyond the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Render an open create_pdf session to a PDF file (step 3 of 3, terminal).' It specifies the verb (render/save), resource (PDF session), and its terminal nature. It distinguishes from siblings by noting that only sessions from create_pdf/add_pdf_content are finalized, and mentions alternative tools for encryption (secure_pdf) and annotations (annotate_pdf).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit usage guidelines are provided: 'Only finalizes sessions created via create_pdf/add_pdf_content.' It also gives when-not-to-use: 'To encrypt an already-saved PDF use secure_pdf; to add annotations use annotate_pdf.' This clearly directs the agent to alternatives and conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
secure_pdfEncrypt / inspect PDF securityADestructive
Encrypt a PDF, report its encryption status, or verify its signatures.
Returns JSON per operation: encrypt→{status, operation, page_count, note}; permissions→{path, is_encrypted, unlocked, permissions}; verify_signatures→ {path, signatures, signature_count}. 'permissions' and 'verify_signatures' are read-only; 'encrypt' writes output_path (overwriting).
Caveat: 'encrypt' rebuilds the document from its text, preserving content and layout but possibly dropping images, embedded fonts and vector graphics (the current API has no in-place encryption). To encrypt a PDF you are authoring, pass the passwords to save_pdf instead.
| Name | Required | Description | Default |
|---|---|---|---|
| password | No | Password used to unlock the file when checking 'permissions' on an encrypted PDF. Unused by other operations. | |
| operation | Yes | 'encrypt' writes a password-protected copy; 'permissions' reports encryption status; 'verify_signatures' checks digital signatures. Only 'encrypt' writes a file. | |
| input_path | No | Source PDF. Required for all three operations. | |
| output_path | No | Destination .pdf for the encrypted copy (overwritten if present). Required for 'encrypt'; unused otherwise. | |
| user_password | No | Open password for the encrypted copy. Required for 'encrypt'. | |
| owner_password | No | Owner/permissions password. Required for 'encrypt'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and readOnlyHint=false. Description adds detailed behavioral context: encrypt overwrites output_path, rebuilds document and may drop images/embedded fonts/vector graphics. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is somewhat lengthy but well-structured with clear sections. Front-loaded with operation summary, then output details, then caveats. Slightly verbose but all sentences add value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Tool has 6 parameters, output schema exists describing return JSON per operation. Description covers output structure, caveats, and usage alternatives. Complete and actionable for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage, baseline is 3. Description adds value by explaining which parameters are required for each operation, and the overwriting behavior of output_path. Goes beyond schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states three distinct operations (encrypt, report encryption status, verify signatures) with specific verbs and resources, and distinguishes from sibling tool save_pdf for authoring use cases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use each operation, provides caveats about image/font loss for encrypt, and recommends alternative save_pdf for authoring PDFs. Full guidance on when-not-to-use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
- Changed
convert_pdf1 field changed- changed
Input schema / properties / max_tokens / descriptionPrevious value: -"Target maximum tokens per chunk. Applies to format='chunks' and 'rag' only; ignored for 'markdown'."New value: +"Target maximum tokens per chunk. Applies to format='chunks' only; ignored for 'markdown' and 'rag' (rag uses heading-aware semantic chunking with a fixed internal budget)."
12 tool updates
v0.12.0- Changed
add_pdf_content8 fields changed- added
Input schema / properties / content / descriptionAdded value: +"Text to draw. Required when content_type='text'." - added
Input schema / properties / content_type / descriptionAdded value: +"'text' draws a text string at (x, y) on the current page; 'new_page' appends a blank page and makes it current." - added
Input schema / properties / content_type / enumAdded value: +[ + "text", + "new_page" +] - added
Input schema / properties / font / descriptionAdded value: +"Font name (e.g. 'Helvetica', 'Courier', 'Times-Roman'). Defaults to Helvetica when omitted." - added
Input schema / properties / font_size / descriptionAdded value: +"Font size in points for text content." - added
Input schema / properties / session_id / descriptionAdded value: +"Session id returned by create_pdf. Must be active." - added
Input schema / properties / x / descriptionAdded value: +"Horizontal position in PDF points from the left edge. Required when content_type='text'." - added
Input schema / properties / y / descriptionAdded value: +"Vertical position in PDF points from the bottom edge (origin is bottom-left). Required when content_type='text'."
- Changed
analyze_pdf6 fields changed- added
Input schema / properties / check / descriptionAdded value: +"Which analysis to run: 'validate' = structural validity with error/warning counts; 'corruption' = damage severity and type; 'compliance' = PDF/A conformance at compliance_level; 'compare' = diff against compare_path." - added
Input schema / properties / check / enumAdded value: +[ + "validate", + "corruption", + "compliance", + "compare" +] - added
Input schema / properties / compare_path / descriptionAdded value: +"Second PDF to diff against. Required when check='compare', ignored otherwise." - added
Input schema / properties / compliance_level / descriptionAdded value: +"PDF/A conformance level to test. Used only when check='compliance'. Letter = conformance class (a/b/u), number = PDF/A part (1/2/3)." - added
Input schema / properties / compliance_level / enumAdded value: +[ + "a1a", + "a1b", + "a2a", + "a2b", + "a2u", + "a3a", + "a3b", + "a3u" +] - added
Input schema / properties / path / descriptionAdded value: +"Path to the PDF file to analyze, relative to the workspace."
- Changed
annotate_pdf10 fields changed- added
Input schema / properties / annotation_type / descriptionAdded value: +"'text' adds a sticky-note marker at (x, y); 'highlight' draws a highlight rectangle of width×height anchored at (x, y)." - added
Input schema / properties / annotation_type / enumAdded value: +[ + "text", + "highlight" +] - added
Input schema / properties / contents / descriptionAdded value: +"Note text for a 'text' annotation. Ignored for 'highlight'." - added
Input schema / properties / height / descriptionAdded value: +"Highlight height in points. Used only for 'highlight'." - added
Input schema / properties / input_path / descriptionAdded value: +"Source PDF to annotate, relative to the workspace." - added
Input schema / properties / output_path / descriptionAdded value: +"Destination .pdf path; overwritten if it already exists." - added
Input schema / properties / page / descriptionAdded value: +"0-based index of the page to annotate." - added
Input schema / properties / width / descriptionAdded value: +"Highlight width in points. Used only for 'highlight'." - added
Input schema / properties / x / descriptionAdded value: +"Horizontal anchor in PDF points from the left edge." - added
Input schema / properties / y / descriptionAdded value: +"Vertical anchor in PDF points from the bottom edge (origin is bottom-left)."
- Changed
convert_pdf5 fields changed- added
Input schema / properties / format / descriptionAdded value: +"Output representation: 'markdown' = one structured Markdown document; 'chunks' = fixed-size token windows; 'rag' = heading-aware semantic chunks for retrieval pipelines." - added
Input schema / properties / max_tokens / descriptionAdded value: +"Target maximum tokens per chunk. Applies to format='chunks' and 'rag' only; ignored for 'markdown'." - added
Input schema / properties / overlap / descriptionAdded value: +"Token overlap carried between consecutive chunks. Applies to format='chunks' only; ignored for 'markdown' and 'rag'." - added
Input schema / properties / password / descriptionAdded value: +"User password to unlock an encrypted PDF before conversion." - added
Input schema / properties / path / descriptionAdded value: +"Path to the PDF file, relative to the configured workspace."
- Changed
create_pdf4 fields changed- added
Input schema / properties / author / descriptionAdded value: +"Document author; stored in the PDF metadata on save." - added
Input schema / properties / page_size / descriptionAdded value: +"Page size for every page in this document." - added
Input schema / properties / page_size / enumAdded value: +[ + "a4", + "a4_landscape", + "letter", + "letter_landscape", + "legal", + "legal_landscape" +] - added
Input schema / properties / title / descriptionAdded value: +"Document title; stored in the PDF metadata on save."
- Changed
extract_entities1 field changed- added
Input schema / properties / path / descriptionAdded value: +"Path to the PDF file, relative to the configured workspace."
- Changed
extract_text3 fields changed- added
Input schema / properties / page / descriptionAdded value: +"0-based page index to extract. Omit to extract every page joined by newlines. Out-of-range indices return an error." - added
Input schema / properties / password / descriptionAdded value: +"User password to unlock an encrypted PDF before extraction." - added
Input schema / properties / path / descriptionAdded value: +"Path to the PDF file, relative to the configured workspace."
- Changed
manage_forms6 fields changed- added
Input schema / properties / fields / descriptionAdded value: +"Field definitions for 'create'. Each: {name, type:'text', x, y, width, height (points), default_value?}." - added
Input schema / properties / input_path / descriptionAdded value: +"Source PDF. Required for 'fill', 'read' and 'validate'; unused for 'create'." - added
Input schema / properties / operation / descriptionAdded value: +"'create' a new PDF with text fields; 'fill' values onto a copy of an existing PDF; 'read' the text content of a form; 'validate' supplied values. 'read' and 'validate' do not write a file." - added
Input schema / properties / operation / enumAdded value: +[ + "create", + "fill", + "read", + "validate" +] - added
Input schema / properties / output_path / descriptionAdded value: +"Destination .pdf path (overwritten if present). Required for 'create' and 'fill'; unused for 'read'/'validate'." - added
Input schema / properties / values / descriptionAdded value: +"Map of field name to value. Required for 'fill' and 'validate'."
- Changed
manipulate_pdf8 fields changed- added
Input schema / properties / degrees / descriptionAdded value: +"Clockwise rotation in degrees (e.g. 90, 180, 270). Required for 'rotate'." - added
Input schema / properties / input_path / descriptionAdded value: +"Source PDF. Required for every operation except 'merge' (which uses input_paths)." - added
Input schema / properties / input_paths / descriptionAdded value: +"Ordered list of PDFs to combine. Required for 'merge'." - added
Input schema / properties / operation / descriptionAdded value: +"Page operation: 'split' one PDF into per-page files; 'merge' several PDFs into one; 'rotate' all pages; 'extract_pages' a subset; 'reverse' page order; 'overlay' one PDF on top of another." - added
Input schema / properties / operation / enumAdded value: +[ + "split", + "merge", + "rotate", + "extract_pages", + "reverse", + "overlay" +] - added
Input schema / properties / output_path / descriptionAdded value: +"Output location, overwritten if it exists. For 'split' this is an existing directory; for all other operations a .pdf file." - added
Input schema / properties / overlay_path / descriptionAdded value: +"PDF stamped on top of input_path. Required for 'overlay'." - added
Input schema / properties / page_indices / descriptionAdded value: +"0-based page indices to keep, in order. Required for 'extract_pages'."
- Changed
read_pdf3 fields changed- added
Input schema / properties / include_page_details / descriptionAdded value: +"When true, also return per-page width, height (in PDF points) and rotation. Off by default to keep the response small." - added
Input schema / properties / password / descriptionAdded value: +"User password to unlock an encrypted PDF. Omit for unencrypted files; if omitted on an encrypted file the tool reports it as locked instead of failing." - added
Input schema / properties / path / descriptionAdded value: +"Path to the PDF file, relative to the configured workspace."
- Changed
save_pdf4 fields changed- added
Input schema / properties / output_path / descriptionAdded value: +"Destination .pdf path inside the workspace. An existing file at this path is overwritten." - added
Input schema / properties / owner_password / descriptionAdded value: +"Owner/permissions password. Encryption is applied only when both user_password and owner_password are provided." - added
Input schema / properties / session_id / descriptionAdded value: +"Session id returned by create_pdf to finalize." - added
Input schema / properties / user_password / descriptionAdded value: +"If set together with owner_password, the saved PDF is encrypted; this is the password required to open it."
- Changed
secure_pdf7 fields changed- added
Input schema / properties / input_path / descriptionAdded value: +"Source PDF. Required for all three operations." - added
Input schema / properties / operation / descriptionAdded value: +"'encrypt' writes a password-protected copy; 'permissions' reports encryption status; 'verify_signatures' checks digital signatures. Only 'encrypt' writes a file." - added
Input schema / properties / operation / enumAdded value: +[ + "encrypt", + "permissions", + "verify_signatures" +] - added
Input schema / properties / output_path / descriptionAdded value: +"Destination .pdf for the encrypted copy (overwritten if present). Required for 'encrypt'; unused otherwise." - added
Input schema / properties / owner_password / descriptionAdded value: +"Owner/permissions password. Required for 'encrypt'." - added
Input schema / properties / password / descriptionAdded value: +"Password used to unlock the file when checking 'permissions' on an encrypted PDF. Unused by other operations." - added
Input schema / properties / user_password / descriptionAdded value: +"Open password for the encrypted copy. Required for 'encrypt'."
TDQS
Scored across 12 tools
Each tool targets a distinct operation (creation workflow, metadata, text extraction, layout analysis, annotation, form management, page manipulation, security, conversion, structural analysis). Descriptions clearly differentiate overlapping areas like extract_text vs convert_pdf vs extract_entities.
All 12 tools follow a consistent verb_noun snake_case pattern (e.g., create_pdf, extract_text, secure_pdf). No mixed conventions or ambiguous names.
12 tools cover the full PDF lifecycle without bloat. Each tool has a clear purpose and the count is well-scoped for a PDF manipulation server.
The surface covers creation, reading, editing, conversion, security, and analysis. Minor gaps like lacking an explicit page deletion tool (handled via manipulate_pdf's extract_pages) or OCR, but core workflows are complete.
Maintenance
Related MCP Connectors
PDF tools + QR over MCP: merge, split, rotate, text, metadata, malicious-content scan.
Privacy-first PDF tools over MCP: merge, split, rotate, delete, compress, protect, inspect.
Convert PDF documents to Markdown for AI agents through an authenticated remote MCP server.
Document processing over MCP: merge, split and compress PDFs, run OCR, extract document text.
Related MCP Servers
- AlicenseAqualityCmaintenanceMCP server for seamless document format conversion using Pandoc, supporting Markdown, HTML, PDF, DOCX (.docx), csv and more.1584MIT
- -licenseNot gradedqualityNot gradedmaintenanceEnables comprehensive PDF analysis and manipulation including page size analysis, chapter extraction, splitting, compression, merging, and conversion to images. Provides both MCP server interface for AI assistants and Streamlit web interface for direct user interaction.-

Nutrient PDF MCP Serverofficial
AlicenseBqualityCmaintenanceEnables low-level PDF object-tree inspection and debugging through natural language, with lazy loading for token efficiency.25MIT- AlicenseNot gradedqualityDmaintenanceMCP server for AI-assisted PDF reading with pagination, search, and page navigation.7 npmMIT