Handing a paper to a language model usually starts with “convert it to Markdown first.” It looks like a format conversion, but in practice it is four problems stacked on top of each other: layout, formulas, tables, and timeouts. pdf-capture-mcp is a multi-stage capture pipeline exposed as an MCP server that hangs off an Agent. A single line of natural language is enough to produce a tidy Obsidian vault bundle.

  1. I

    Extract

    PyMuPDF for plain layouts; Marker for complex pages; MinerU for multi-column and InDesign files.

  2. II

    Clean

    Hyphenation, control characters, image paths, math delimiters — only deterministic, information-preserving fixes.

  3. III

    Audit

    Beyond statistical gates, a content audit that grew out of one 75-page paper that fooled every check.

  4. IV

    Repair

    Cross-channel comparison against the PDF text layer; report failures rather than guess at numbers.

  5. V

    Vault

    Main text, figures, chunks, and the QC record in one directory; drag the folder straight into Obsidian.

Three engines, on demand

Three engines are layered by need. The base install is ~80 MB and works out of the box. Complex layouts pull in Marker; multi-column layouts add MinerU in its own virtualenv so the main install stays clean. Vision models are optional — Tongyi, Zhipu, Moonshot, OpenAI, or a local Ollama — used to fill in tables and figure text. API keys live only on disk with chmod 600; they never echo back in responses.

A folder both Agents and Obsidian can read

Each conversion writes a folder by default. An Agent reads the entry README.md to know where to start; a human can drag the whole folder straight into an Obsidian vault. Identity is content-addressed: doc_id is a hash of the PDF, so re-running the same file idempotently overwrites the previous output rather than following filenames.

<slug>/
├── <slug>.md          Main text, named after the folder
├── README.md          Entry point: abstract, file table, chunk schema
├── images/            Extracted figures
├── tables/            Tables as standalone CSV
└── data/
    ├── chunks.jsonl   Semantic chunks with heading paths
    ├── metadata.json
    └── qc_report.json Audit and repair record

mode="auto" is the default: under 15 pages returns in place; longer PDFs return a job_id and an ETA immediately, with get_job_status walking through classify → extracting → table_extraction → qc → done. Results are always written to disk first, so a disconnected client never loses the run. Scanned PDFs are forced through OCR; failed page-windows are listed in missing_segments — loss is visible, never silent.

QC rules against silent text loss

QC runs in two tiers. The first inspects character density, headings, formulas, and table coverage to catch gross failures. The second grew out of one real 75-page audit: every actual defect in that paper had slipped past the statistical gates.

RuleWhat it catchesAuto-repair
MD-102Control characters inside cell wraps splitting Enlightenment apartRe-stitch
MD-104Scientific notation splitting across cells, lost decimal pointGeometric recovery; digits untouched
MD-105Header row fused with the first data rowToken-geometric reconstruction, token conservation
MD-108Brackets in citation links being read as math delimiters by KaTeXStrip escapes only inside the link
MD-201De-hyphenated comparison against the PDF text layer, catching body text lossInject only when the gate passes

Text structural defects are handled through repair-or-report: we attempt a fix against the PDF text layer, and every step has a machine-verifiable gate. MD-104’s recovered value, after stripping the decimal point, must concatenate back to the original fragment — we recover the lost dot, never alter digits. When a gate fails, the candidate stays in qc_report.repairs with its proposal; guesses never enter the body. Merged cells that geometry cannot resolve are escalated to the configured vision model; if Markdown cannot express a rowspan, we fall back to HTML. Any critical finding downgrades PASS to WARN.

Talking to your Agent

With the MCP configured, plain natural language is enough:

Convert ~/Downloads/paper.pdf to Markdown.

Extract every table from this report.

Is this PDF a scan?

For Cursor, Claude Desktop, and similar clients, attach it through mcp.json:

{
  "mcpServers": {
    "pdf-capture": {
      "command": "uvx",
      "args": ["pdf-capture-mcp[marker]"]
    }
  }
}

On networks where Hugging Face downloads are slow or restricted, prime the model cache first: ask the Agent to run download_models, and set HF_ENDPOINT=https://hf-mirror.com if needed. The full tool list, environment variables, and troubleshooting live in the project README.

MIT. Python 3.11+. MinerU runs as a separate subprocess and is not pulled into this package.