Skip to main content

KlarPDF MCP bridge

KlarPDF's PDF engine as MCP tools — for Claude Code, Claude Desktop, Codex CLI, Gemini CLI, and any other client that speaks MCP over stdio. Nineteen tools: read a document without pulling it whole into context; transform it by splitting, merging, reordering, rotating, deleting pages, filling forms and annotating; and redact it destructively with cross-engine verification.

Claude Code, on Linux or macOS:

pipx install klarpdf                                  # or: uv tool install klarpdf
claude mcp add klarpdf -- ~/.local/bin/klarpdf-mcp

That is the whole install. The first line is the same everywhere — only the path and the client command change: on Windows where klarpdf-mcp prints the path to use, Codex CLI and Gemini CLI take their own mcp add, and Claude Desktop installs a bundle rather than a command. Quick setup has each of those combinations written out.

Everything below is the reference — what each tool guarantees, how to restrict what the server may touch, and what redaction does and does not promise.

It is independent of the KlarPDF app and needs no GUI components. It runs on macOS, Linux and Windows.

Your PDFs stay where they are. An agent works on the file on your disk — nothing is uploaded and no third-party service sees it, which is the point of running the engine locally rather than sending documents somewhere to be processed. The server itself makes no network connections: stdio is the only transport and there is no listening port. What a tool returns — a page's text, a rendered image — goes back to your model like any other tool result, so the usual care about what you hand a hosted model still applies.

It does not understand your documents. Every tool here is mechanical: search matches literal text rather than meaning, extract_text returns what is on the page rather than a summary, and annotate and the redactions take boxes and strings rather than questions. Working out what a clause means, which name matters, or what ought to be removed is the model's job — this server's job is to hand it accurate material and then do exactly what it is told.

What you need

Python 3.11 – 3.14 Any version in that range; each is tested in CI on Linux and Windows.
A way to install it pipx or uv — either puts the bridge in an environment of its own, which is what you want: it pins all 29 of its dependencies exactly, so it will conflict with anything you co-install. Neither is required: install.py below needs only the Python you already have. Plain pip works too, into a virtualenv you made for it.
uv, again Additionally required for the .mcpb bundle in Claude Desktop. Desktop launches it with uv run, so uv must be on the PATH Desktop sees, which is not always the PATH your terminal has. Nothing warns you if it is missing; the symptom is the server failing to start.
An MCP client The app your AI assistant runs in — Claude Code, Claude Desktop, Codex CLI, Gemini CLI and others. It starts klarpdf-mcp as a local subprocess and relays the model's tool calls to it.
poppler-utils Optional. Adds a second, independent engine to redaction's verification step. Without it, redaction still verifies — with PyMuPDF alone. See What redaction guarantees.

No GUI toolkit is installed. The bridge's only dependencies are PyMuPDF and the MCP SDK.

Install

The two lines at the top of this page are the whole install. pipx puts the bridge in an isolated environment and its klarpdf-mcp command on your PATH, so nothing else on your machine is touched; uv tool install klarpdf does the same job. To try it once without installing anything, uvx --from klarpdf klarpdf-mcp --help runs it straight from the package.

The rest of this section is the detail behind that — what you get, what to avoid, and how to work from a clone instead.

Every install gets the exact versions we test and scan. The published package pins all 29 of its dependencies at ==, rather than declaring floors the way a library would — because this is an application, and an application installed into an environment of its own has nothing to conflict with. So pipx install klarpdf resolves to the set our CI runs against and our weekly audit scans, not to whatever happens to be newest that day.

The flip side, worth knowing before you reach for plain pip: those exact pins mean pip install klarpdf into a shared environment will conflict loudly with anything wanting a different version. That is the intended behaviour, not a bug. If you use pip, give it a virtualenv of its own.

Check that it worked, and note the path — you will need it in a moment:

klarpdf-mcp --help
which klarpdf-mcp        # Windows: where klarpdf-mcp
# -> ~/.local/bin/klarpdf-mcp

With neither pipx nor uv

Download install.py from the latest release and run it with any supported Python:

curl -LO https://github.com/utyagi24/klarpdf/releases/latest/download/install.py
curl -LO https://github.com/utyagi24/klarpdf/releases/latest/download/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS     # macOS: shasum -a 256 -c
python3 install.py                                # Windows: py -3 install.py

It makes a virtual environment of its own, installs into it, proves the server starts, and prints the exact line your client needs — then writes an uninstall.py beside it. It touches nothing else: not your PATH, not your shell profile, not your system Python. --client claude-code also registers the server for you, using that client's own CLI; --help lists the rest.

The checksum step is worth the two extra lines. curl … | python3 - works and the file says so when you do it, but there is then no file to verify.

From a clone instead

For working on the bridge, or for pinning to a specific tag:

git clone https://github.com/utyagi24/klarpdf.git
cd klarpdf
git checkout v<version>               # a release tag; `git tag` lists them
python3 -m venv .venv                 # Windows: py -3 -m venv .venv
source .venv/bin/activate             # Windows: .venv\Scripts\activate
pip install -r requirements-mcp.txt   # the exact versions we test against
pip install -e .                      # puts `klarpdf-mcp` inside the virtualenv

Run both pip lines, in that order — the first installs the audited set, the second installs klarpdf-mcp itself with its requirements already satisfied. which klarpdf-mcp then reports /path/to/klarpdf/.venv/bin/klarpdf-mcp, which is the path your client needs, since a virtualenv puts nothing on the PATH your client sees.

You will not normally run klarpdf-mcp yourself — your client starts it and talks to it through the subprocess's stdin and stdout.

Connect it to your client

Every client has its own way of configuring MCP servers, but they all ask for a command to run. For our MCP server that command is klarpdf-mcp.

That command is the whole of KlarPDF's entry in that list. There is nothing else to fill in: no arguments, no environment variables, and no URL, port or token — those belong to servers that run over a network, and this one does not. Your client starts klarpdf-mcp as a subprocess and the two talk over its stdin and stdout. (Two optional switches can restrict what it is allowed to do — see Limiting what the server can do.)

Use the full path that which klarpdf-mcp printed. A pipx or uv tool install already puts klarpdf-mcp on your PATH, so the bare name often works — but often is the problem: a client launched from an icon or a login item inherits a different PATH from your terminal, and the symptom is a server that simply fails to start with nothing explaining why. The absolute path always works, so the examples below use one. Substitute yours:

Installed with Find it with Typically
pipx / uv tool which klarpdf-mcp · Windows: where klarpdf-mcp ~/.local/bin/klarpdf-mcp on Linux and macOS. On Windows it depends on where your pipx/uv puts its shims, so read it off rather than assuming
a clone + virtualenv the same, with the virtualenv active /path/to/klarpdf/.venv/bin/klarpdf-mcp — Windows: ...\.venv\Scripts\klarpdf-mcp.exe

Claude Code

Install first (above), then:

claude mcp add klarpdf -- ~/.local/bin/klarpdf-mcp

If you run Claude Code from inside this repo, skip that command. A .mcp.json at the repo root already describes the server, and Claude Code offers it when you start here — approve the prompt and you are configured. (It is offered rather than switched on silently, which is why checking it in is safe.) That file calls the bare klarpdf-mcp, so activate the virtualenv before you start Claude Code: source .venv/bin/activate.

Confirm with /mcp: it should say klarpdf — 19 tools. If it says failed, run the command by hand in the same shell you launch Claude from; the error is almost always command not found (nothing installed, or a different virtualenv active) or No module named mcp (installed the package but not its dependencies).

Claude Desktop

Option A — the .mcpb bundle. Install it in Claude Desktop as an extension. You can get the bundle in two ways:

  • Download itklarpdf-<version>.mcpb from the latest release, with SHA256SUMS alongside it. No clone and no Node required.
  • Build from a checkout — run python packaging/mcp/mcpb/build_mcpb.py. This needs Node, since the packer itself is a Node tool. The script's header documents exactly what goes into the bundle and what's deliberately left out.

Either way, you also need uv. The bundle contains only source — no interpreter, no dependencies of its own — so Claude Desktop relies on uv run to actually launch the server. Downloading spares you Node, but not uv; make sure it's on the PATH that Desktop sees before you open the file.

This also sets Option A apart from every other path here: it installs online. At install time, uv fetches wheels from PyPI for your machine, resolved against a hashed uv.lock shipped inside the bundle — so you get the exact package set we reviewed, not whatever PyPI happens to resolve that day. That lock does pin a handful of platform-specific packages that our weekly pip-audit never sees. If that residual risk isn't acceptable, use Option B.

Option B — edit the config. Add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):

{
  "mcpServers": {
    "klarpdf": {
      "command": "~/.local/bin/klarpdf-mcp"
    }
  }
}

Codex CLI

codex mcp add klarpdf -- ~/.local/bin/klarpdf-mcp

Or write it into ~/.codex/config.toml yourself:

[mcp_servers.klarpdf]
command = "~/.local/bin/klarpdf-mcp"

Gemini CLI

gemini mcp add klarpdf ~/.local/bin/klarpdf-mcp

Or edit ~/.gemini/settings.json (or .gemini/settings.json for a single project), which uses the same mcpServers block Claude Desktop does:

{
  "mcpServers": {
    "klarpdf": {
      "command": "~/.local/bin/klarpdf-mcp"
    }
  }
}

Any other client

Clients differ in where the configuration lives, not in what it has to say. Claude Desktop and Gemini CLI share the mcpServers JSON block above; Codex CLI uses the [mcp_servers.<name>] TOML table; others vary again. All of them want one thing from you — a command to run. Point yours at your ~/.local/bin/klarpdf-mcp, add args if you want the switches below, and ignore every field about URLs, ports and tokens, because this server has none.

Limiting what the server can do

By default the server can read and write any file you can. That is the honest default rather than a lax one: it is a subprocess you launched, running as you, and a client that can start it can already read your disk. Two switches narrow that when you want a smaller blast radius — a shared machine, an agent you are still learning to trust, a directory of client documents.

Switch Environment variable What it does
--read-only KLARPDF_MCP_READ_ONLY=1 Registers only the seven read tools. The twelve transform and redaction tools are never advertised, so the model does not see them and cannot ask for them.
--allow-root DIR KLARPDF_MCP_ALLOW_ROOTS Confines every path — inputs and outputs alike — to that directory tree. Repeatable on the command line; the variable takes a list separated by your platform's path separator (: on macOS and Linux, ; on Windows).

Pass them wherever your client names the command:

{
  "mcpServers": {
    "klarpdf": {
      "command": "~/.local/bin/klarpdf-mcp",
      "args": ["--read-only", "--allow-root", "/Users/me/Documents/contracts"]
    }
  }
}

Three details that matter in practice:

  • Containment is checked on the resolved path, through the same file-identity code the app uses, so a symlink pointing out of an allowed root, a .. escape, and a case-different spelling on Windows are all refused. A path that does not exist yet — an output you are about to write — is judged by its parent directory.
  • --allow-root wins over the variable when both are set. --read-only is on if either asks for it, and neither turns it back off.
  • A restriction is announced on stderr when the server starts, naming what is in force. Never on stdout, which carries the JSON-RPC stream — one stray line there ends the session.

Writes are on by default because no write tool can destroy data by construction: each needs an explicit new output path, and in-place save is never exposed. --read-only is the cautious opt-out, not the recommended setting.

Password-protected PDFs

Every tool takes an optional password. Call get_info first — it reports needs_password: true for a document that will not open without one, and reports the encryption and permissions of a document that opens but is restricted.

A missing or wrong password fails the call with a message naming the file and telling you to call again with password. The server never prompts: there is nobody behind an MCP call to answer a prompt, so prompting would hang the client instead of asking anyone anything.

Reading a tool's full contract

Each tool's description carries what you need before calling it. The reference half — the field-by-field catalogue for redact_text, the counting and scope rules, how to feed search hits to redact_regions — is published as an MCP resource instead:

klarpdf://docs/redact_text
klarpdf://docs/search

This is not organisation for its own sake. Claude Code truncates a tool description at 2,048 characters and appends … [truncated] with no error, so 69% of redact_text's documentation used to be discarded in transit — and it was the half describing what the reply means. Resource reads are capped at 100,000 characters, so that material now has somewhere to live. The resource serves the tool's live registered description plus the appendix, so it can never drift from what the tool actually advertises.

The tools

Page numbers are 1-based everywhere, matching what a reader sees. An out-of-range page is an error, never a silent clamp. Every tool takes an optional password — see Password-protected PDFs.

Read
get_info Pages, size, page sizes, encryption + permissions, has-text-layer, outline. Call it first.
get_outline Bookmarks as {level, title, page}.
search Hits with page, snippet, box, and whether the text is invisible on the page. match_case, whole_words.
extract_text Text of named pages.
render_page One page — or one clip region of it — as a PNG image block.
get_form_fields Fillable fields, one entry per occurrence, with each one's checkbox on-state and read-only / required / multiline / max-length.
get_annotations Every mark on the page — anyone's — with its note, colour and boxes; mine / editable say who wrote it and whether the app can edit it.
Transform — writes a new file
extract_pages Named pages out as one new document — the "give me pages 10-20" tool.
delete_pages · reorder · rotate Page-set edits; bookmarks follow their pages.
split · merge Cut into several files by print-dialog ranges ("1-3", "5-") / concatenate; merge renames colliding fields.
fill_form · flatten Fill (still editable; checkboxes take true or their own export state, anything else is an error) / bake in (no longer editable). fill_form warns on an XFA form and on read-only fields.
export_images Rasterise pages — or one clip region of each — to png/jpg files.
annotate Write highlights / underlines / strike-throughs, each able to carry a note. Takes boxes, not queries; merges with markup already there rather than stacking.
Redact — destructive, verified
redact_text Remove every match of a query — or of several queries, in one verified pass.
redact_regions Remove rectangles; a search hit's box feeds straight back in.

What the write tools guarantee

  • Your input is never written to. Every write tool requires an explicit out, in-place save is not exposed, and the refusal resolves through the same path-identity check the app uses — so a symlink, a .. segment, or a case-different spelling on Windows cannot slip the input back in as the destination. "The source is left byte-identical" is a test, not a claim.
  • Nothing else is clobbered either. An existing output is refused unless you pass overwrite: true.
  • Lossless for content; for document structure, only if the page set is unchanged. Text layer, form fields, annotations and bookmarks always survive, and bookmarks are re-pointed at pages' new positions rather than left dangling. But a tool that moves pages (reorder, delete_pages, extract_pages, split, merge) builds a new document, and the accessibility structure tree, /Perms, the /Names tree and encryption do not survive that — they are document-level, and copying pages does not copy them. A tool that leaves every page in place (fill_form, flatten, the redactions) edits a copy of the original instead and keeps all of it. See PLAN.md §Key design idea for the table.
  • A mistake is an error, not a quiet partial success. An unknown form-field name, a reorder that is not a full permutation, a delete_pages that would empty the document, a redact_text that matches nothing — all fail loudly rather than writing something plausible.
  • A misspelled parameter is an error too. Every tool rejects an argument name it does not declare, naming it, suggesting the near miss (case-insensitively, so PAGES is answered with pages), and listing what it does accept — before reading or writing anything. This is not decoration: the MCP SDK's default is to drop an unrecognised key, so redact_text called with querys instead of queries used to redact only the other query and report an unqualified success — residual_matches: 0, cross-engine verified — on a file that still held the value you asked it to remove. Nothing downstream can catch that, because the record of what you asked for is gone before the tool runs (PLAN.md §M106).

Clipping to a region

render_page and export_images both take an optional clip[x0, y0, x1, y1] in page points, the same coordinate space search reports hits in and redact_regions consumes them from. Boxes are this server's native currency, so region→image is the "I know where" workflow it already serves, minus the destruction.

The composition worth knowing: searchrender_page(clip=…) shows a person the actual pixels of what is about to be deleted. Every other safety mechanism here is textual; this is the one that makes the preview visual, on tools whose docs already say to search before you redact. It also reads a stamp, signature or single table cell at 300 dpi without paying to render the page around it.

A search hit carries boxes, one per line, so a match wrapping a line break has several. Pass one, or their bounding box to see the whole match. clip takes a single rectangle where redact_regions takes the list, and the asymmetry is deliberate: a union spanning two lines picks up whatever sits between them, which is helpful when looking and is data loss when deleting.

Rotation is handled for you. search reports boxes in the page's unrotated space — the same coordinates at /Rotate 0 and /Rotate 90 — and clip reads them there, so a hit feeds straight back whatever the rotation. The image is the region as displayed, so a quarter turn swaps its width and height. One consequence worth knowing: get_info.page_sizes reports displayed dimensions, so on a turned page a perfectly valid box can extend past the width shown there.

The pixel size rounds outward to whole device pixels, so it is ceil(x1 x dpi/72) - floor(x0 x dpi/72), not (x1-x0) x dpi/72. A 100 pt square at 150 dpi comes back 209 px, not 208.33 — and exactly 100 px at 72 dpi, where the scale is 1:1. Expanding outward is deliberate: no partial pixel of the region you asked for is dropped, which is what you want from a crop. Just don't assert on the naive formula.

Every exported file carries its page number, zero-padded to the document's page count — <stem>-03.png from a 20-page file, <stem>-003.png from a 572-page one, whether you export one page or twenty. The width comes from the document rather than from the pages you asked for, so two exports from one file into one directory agree and sort correctly. name chooses the stem. That matters for the use clip exists for: cutting two regions out of one page needs name: "card_front" then name: "card_back", or both calls want the same filename and the second is refused. name is a plain filename stem, never a path: no separators, no .., no extension.

A clip that runs off the edge of the page is an error, not a smaller image. PyMuPDF would happily intersect it and hand back a cropped pixmap; render_page returns an image block, so its reply has nowhere to say that it did. The error names the page's rect instead, so the fix is one step rather than a guess. export_images checks every page in the set before writing anything — page sizes vary within a document, and a clip that dies on page 7 must not leave six files behind.

Marking up, and the review hand-off

annotate writes highlights, underlines and strike-throughs; get_annotations reads back every mark a document carries, including ones made in Acrobat, Preview or Edge. Both are worth having on their own — "underline every termination clause", "summarise the review comments in this contract" — and together they make a hand-off an agent cannot do alone:

  1. Propose. Locate what matters with search, then annotate it, perhaps one colour for certain and another for needs a look, with a note on each saying why. Nothing is deleted.
  2. Dispose. A person opens the output in KlarPDF. Every mark written here is an ordinary editable mark: recolour it, extend it, delete it, reply in its note. That review surface already exists and is better than any tool reply.
  3. Act. Read the reviewed file with get_annotations, filter on what they left, and go.

annotate takes boxes, not queries. It does not search and it has no idea what a name or a termination clause looks like — that judgement is the caller's, and a PDF engine that pretended otherwise would be guessing. This is the same line redaction's variant scan draws when it reports rather than matches.

Colour is the reviewer's convention, never the tool's. Nothing here decides that orange means delete. If deletion is the outcome, filter get_annotations and pass the boxes to redact_regions, which verifies the removal as it always does. There is deliberately no "redact what I highlighted" shortcut: that step should be one you took, not one a colour triggered. The boxes need no adjusting on the way — both tools work in unrotated page points, at any page rotation.

A repeat call merges rather than stacking. Re-marking a span in the same colour is a no-op, a different colour takes the span over, and notes are carried onto the survivor — the app's own rules, because it is the app's own function. So retrying a call is safe.

What redaction guarantees, and where it stops

The text is physically removed, not covered with a black box, and the redaction annotation is consumed so there is nothing left in the output to delete and reveal what was under it. Then the written file is re-read and checked: with PyMuPDF always, and with Poppler's pdftotext when it is installed — a different engine from the one that did the removing. If anything is still recoverable, the output is deleted and the call fails. A path coming back always names a file that was verified.

The guarantee covers the text layer. Text that is part of a scanned image is pixels: the region is removed along with the image, but there is no text to verify, so verified_text comes back empty and the result says so. cross_engine_verified tells you whether the second engine ran at all — install poppler-utils for the stronger check.

The two tools prove different things, and redact_regions proves the narrower one. redact_text removes what you named and re-checks the whole document to prove it caught every occurrence. redact_regions removes what is there — you said where, so the boxes are the whole of the request, and the check confirms those boxes are empty, nothing wider. Read verified_text on a region redaction: it lists what actually came out, which is often more than you aimed at, since a rectangle over a name may take the address under it too. If you are removing PII rather than blanking an area, search those strings before sending the file on, or use redact_text.

Preview with search before redact_text. Matching is the app's find-bar behaviour, so with whole_words off a search for "Smith" also matches inside "Smithsonian" — and this tool deletes what it finds.

residual_literal counts occurrences, not spellings. It is the number of times the query is still in the file in a form whole_words did not match, with residual_literal_forms naming each spelling, its own count and its pages. A single spelling can account for a dozen occurrences on a long document, so read the integer as "how much is still there" and the list as "what it is".

When a redaction lands inside a longer word, the reply says so. That is the one way this tool damages text you never asked about: redacting Male with whole_words off also takes the male out of Female, leaving Fe. It is not filtered out, because matching inside longer words is exactly what you want for an identifier buried in a machine tag — but it is reported. partial_word_matches names the term, the word it came out of, and what that word now reads, and a warning repeats it. None of the residual checks can find this on their own: they are all scoped to the query, and the query was removed exactly as asked.

A redacted file can be larger than its source, and the reply explains why. Erasing pixels inside an image means decoding it; re-compressing lossily would degrade exactly the area being redacted, so the image is stored losslessly, and a photograph held losslessly is much bigger than the same photograph as JPEG. images_recoded names each one by its PDF filter (DCTDecodeFlateDecode) with the embedded stream size before and after, so the numbers reconcile against the output file. Nothing is duplicated — only the placements a box actually overlaps are re-encoded.

Removing several things? Use queries, not several calls. The argument is data hygiene rather than convenience: every write needs a fresh out, so a chain of six redactions strews five intermediate files, each a partially-redacted copy still holding the values you have not reached yet, and each one yours to remember and delete. One pass writes one file. It also retires an ordering trap — chained calls had to run the longest query first, or a shorter one consumed part of a longer match and left fragments behind. In one pass every box is computed against the intact source before anything is applied, so the order of the list cannot matter. Overlapping terms (607347469 203 1 and 607347469) are the case this is for, not an edge case to avoid.

whole_words: true means whole token, and for a single-token query that is usually the wrong choice. A "word" is a run of characters between spaces, so 220885-1063303 with whole_words on does not match <AccountNumber:220885-1063303> — the entire tag is one word. Machine-readable tags, filenames, URLs and key:value pairs all have that shape, and a bill that stamps its account number into such a tag will keep it through a redaction that looks like it worked. For a query with no spaces, whole_words: false is both safe (there is no second word to over-match) and the mode that finds the value wherever it is embedded.

That case is why the reply carries two fields worth reading rather than just a success:

  • residual_literal — places the query still appears literally that the whole_words setting does not match, each named in warnings. Deliberately a warning and not a failure: redacting whole-word "Smith" legitimately leaves "Smithsonian". The named token is what separates the two — 'Smithsonian' reads as fine, '<AccountNumber:220885-1063303>' does not. This check shares nothing with the matcher, which is the point: the older verification did, so it could confirm the matcher's own blind spots as clean.
  • invisible_matches — removals that were never visible on the page (white-on-white, transparent, painted over). They are gone, but they tell you this document stores data where no reader, and no before/after render comparison, would ever find it. search flags the same thing per hit as invisible.
  • residual_normalized — spellings still in the file that differ from the query only in separators: 6073474692031 against 607347469 203 1, 08/24/1970 against 08-24-1970, or an identifier broken by a line wrap. A literal scan sees none of these, so redacting one form used to report the file clean while the other stayed in it. Nothing is deleted for a variant — whether two spellings are one value is a fact about the document that only you have. [] and null mean different things. [] is "the scan ran and found nothing"; null is "the scan did not run", which happens for a short unpunctuated query like 000000, where matching across separators finds coincidence rather than spellings. null comes with a warning saying so, because a feature that exists to close an invisible failure must not go quiet in a way that reads as reassurance. A query you punctuated — 999 99 9999, AB 12 CD — is always scanned: the separators are you saying it is a structured value.
  • queries — the per-query breakdown when you passed a list. Every field above is reported inside it, per query, beside that query's own matches, because flattening six queries' counts into one set would report the last one's numbers as the whole call's. The shape follows which parameter you used, not how many queries you happened to put in it: a one-element queries list still comes back as a list, so a caller iterating a variable-length list gets one shape to parse. A query that matches nothing does not fail the call when another matched — it comes back matches: 0 with a warning, because failing would delete an output that correctly removed the others.
  • residual_scope — the pages the two scans above actually read. Every page, unless you passed pages; then the scans cover that slice, exactly as the redaction did, and a warning says so. Everything in a reply describes the operation you asked for — the tool never mixes page-scoped and document-wide answers in one response. Don't read pages_redacted as a substitute: it lists only where boxes landed, which is a smaller set ([1] for a call that scanned [1, 2, 3]).
  • matches vs boxes_redacted — different numbers, both correct. matches sums each query's own hits, so text two queries both matched counts twice; boxes_redacted counts distinct rectangles applied. Neither is "how many identifiers came out": a short query matching inside a longer query's match makes two real boxes over one piece of text.
  • query_terms — the per-term match breakdown when whole_words is off and the query has several words, with a warning when one term did most of the deleting and the phrase itself is rare. This is the over-redaction counterweight: everything else here proves the query is gone, and all of it is silent when a query removed far more than you meant. It is also the only warning you get, because destroyed content leaves no trace in the output — the only record it was ever there is the input, which is never modified.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

klarpdf-0.19.0.tar.gz (252.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

klarpdf-0.19.0-py3-none-any.whl (247.3 kB view details)

Uploaded Python 3

File details

Details for the file klarpdf-0.19.0.tar.gz.

File metadata

  • Download URL: klarpdf-0.19.0.tar.gz
  • Upload date:
  • Size: 252.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for klarpdf-0.19.0.tar.gz
Algorithm Hash digest
SHA256 d130c6d105fafcb87bfcd9cd14a108e59255a9cbb7706952da85b4e785f9e20e
MD5 297366e74220c4e8d5518ff23105f01b
BLAKE2b-256 2c9ce61a96f5e47900e75503e2f2728a71907da7ea532e9649a165cc8541da8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for klarpdf-0.19.0.tar.gz:

Publisher: publish-pypi.yml on utyagi24/klarpdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file klarpdf-0.19.0-py3-none-any.whl.

File metadata

  • Download URL: klarpdf-0.19.0-py3-none-any.whl
  • Upload date:
  • Size: 247.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for klarpdf-0.19.0-py3-none-any.whl
Algorithm Hash digest
SHA256 634350196a9171bf0766d041cb4e502192d416cdeb6522f2bd5b5e5a749ab185
MD5 ddad8dc56da44ce48992714deb1266c9
BLAKE2b-256 c632cc43e382e3b0e3f42cda3e3ddbca19aec69cbe39096ebda95bd44556e038

See more details on using hashes here.

Provenance

The following attestation bundles were made for klarpdf-0.19.0-py3-none-any.whl:

Publisher: publish-pypi.yml on utyagi24/klarpdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.19.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page