Local, private PDF to Markdown for agents: geometry-first parsing, OCR only where needed.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
A PDF decompiler, not an image reader. PDF β Markdown in the browser or Node, in milliseconds for born-digital pages β with on-device OCR spent only on the pixels the text layer can't explain.
Try it in your browser β β drop any PDF; it never leaves the tab.
Every PDFβMarkdown tool sits at one of two extremes:
| Approach | Speed | Quality | Problem |
|---|---|---|---|
Text-layer extraction (pdfminer, pdf.js getTextContent) | instant | poor | PDF has no paragraphs, headings, tables or reading order β you get a soup of positioned strings |
| Render + OCR / vision model (Textract, LlamaParse, VLMs) | slow, paid, cloud | high | Rasterizes a page that was already digital, then asks a model to re-read pixels the file could have told it exactly |
~80% of real-world PDFs are born-digital: every glyph's exact coordinates, size and font are already in the file. pdffr treats PDF as what it is β a drawing program β and decompiles the drawing back into structure:
**bold**/*italic*/<sup>, math fonts and sub/superscripts transliterated to $LaTeX$, rotated text re-framed upright, running header/footer stripping, hyphenation repair. No rasterization. Milliseconds per page.pdffr/node runs the identical pipeline on the server or the command line, with @napi-rs/canvas standing in for the DOM.pdfjs-dist and tesseract.js are peer dependencies; @napi-rs/canvas is an optional peer used only by the Node entry.
decompile(input, options?) β Promise<DecompileResult> β input is an ArrayBuffer, Uint8Array, Blob or File. Options: ocr, lang, concurrency, pool, onPage, onEvent, pdfWorkerSrc.decompileFile(path, options?) β Node only.warmOcr(lang?) / terminateOcr() β pre-load or shut down the shared tesseract pool.ocrPool(lang?) β the shared OcrPool; pass your own via options.pool to control worker count.runPipeline(buffer, emit, { ocr, concurrency, escalate }) β the streaming core, if you want raw events.blocksToMarkdown(blocks) β render typed blocks yourself.setPdfWorkerSrc(url) β configure pdf.js's worker.Types: Block, ListItem, Run, Region, Rules, PageState, Stats, PipelineEvent.
| Package | What it is |
|---|---|
pdffr-mcp | MCP server for Claude Desktop / Claude Code / Cursor / any agent: pdf_to_markdown, pdf_outline, pdf_tables β listed on the MCP Registry |
pdffr-langchain | LangChain.js document loader β one Markdown Document per page |
pdffr-llamaindex | LlamaIndex.TS reader β one Markdown Document per page |
langchain-pdffr (PyPI) | Python: pdffr.convert() and a LangChain PdffrLoader, driving the CLI (needs Node 20+) |
Structure pass: rotated runs re-framed upright (a dominant rotation turns the whole page; a minority is a sidebar group) β buildLines (math spans β LaTeX) β orderRuns (XY-cut: tall prose gutter β vertical cut; largest whitespace band β horizontal cut; ruled and aligned tables detected first as atomic boxes) β toBlocks (headings, lists with nesting, paragraphs by leading, display math, tables, furniture stripping) β markdown.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/pdffr)<a href="https://allmcps.com/mcp/pdffr"><img src="https://allmcps.com/api/badge/pdffr?style=directory" alt="Pdffr on AllMCPs" /></a>