# PDF / DOCX Extract

> "Give me the text of this PDF, Word, PowerPoint or Excel file as clean Markdown, plus pages, headings, tables and counts as JSON."

- Status: MCP package (hosted access coming soon)

## The question it answers

"Give me the text of this PDF, Word, PowerPoint or Excel file as clean Markdown, plus pages, headings, tables and counts as JSON."

## Use it when

- An agent has a text-based PDF or an Office file and needs readable text and a rough structure.
- You want the same input to give the same output every time (no LLM involved).

## Do not use it when

- The PDF is a scan. There is no OCR for PDFs. You get little or no text. (`extract_image_ocr` works on a single PNG/JPEG you provide, if the optional OCR package is installed.)
- You need exact table structure or layout. Tables are guessed from whitespace (PDF) or read from the document XML (DOCX, XLSX).
- The file is over 40 MB.

## Example call

There is no public HTTP route yet, so no curl example. Locally:
```bash
npx tsx src/cli.ts extract --url https://www.irs.gov/pub/irs-pdf/f1040.pdf
```
MCP: `extract_document {"url":"https://www.irs.gov/pub/irs-pdf/f1040.pdf","kind":"pdf","maxPages":5}`. You can instead pass `base64` (with an optional `filename`). Local `path` input works only for files in the server's sandbox folder.

Params: `path`, `url`, `base64` (one required), `filename`, `kind` (`pdf`, `docx`, `pptx`, `xlsx`; detected from the name or file header if left out), `maxPages` (1-200, default 50), `ocr`, `useCache`. `extract_batch` takes up to 50 items.

## Example response (trimmed; shape follows the code)

```json
{
  "ok": true,
  "kind": "pdf",
  "source": { "type": "url", "name": "f1040.pdf", "bytes": 220237 },
  "markdown": "## Page 1\n\nForm\n1040 2025U.S. Individual Income Tax Return\n...",
  "structured": {
    "meta": { "title": null, "author": null, "totalPages": 2, "contentSha256": "3d31c226..." },
    "parts": [ { "index": 1, "label": "Page 1", "text": "Form\n1040 2025 ..." } ],
    "sections": [ { "heading": "Page 1", "level": 2, "text": "..." } ],
    "tables": [],
    "wordCount": 2225,
    "charCount": 10151
  },
  "limits": [ "Deterministic local extraction — no LLM rewriting.", "PDF text layer only — scanned/image PDFs need OCR (optional tesseract.js)." ],
  "ocrUsed": false,
  "cacheHit": false,
  "billing": { "sku": "pdf-docx-extract.extract", "plannedPriceUsd": 0.02, "x402Enabled": false },
  "toolVersion": "0.1.0"
}
```
On failure you get `"ok": false` and an `error` string instead of content.

## Data sources and licence

Only the document you send or link. We do not claim any licence over the content. The URL fetch refuses private and internal addresses.

## Freshness and caching

Extractions are cached by file content, page limit and OCR flag, with no expiry, and the cache holds the full extracted text. Send `useCache:false` to skip reading the cache (the result is still saved).

## Honest limits

- PDF: text layer only (unpdf / pdfjs). Reading order and table detection are heuristic.
- DOCX: via mammoth; SmartArt, charts and complex layout lose detail; headers, footers and comments may be incomplete.
- PPTX: slide text only. XLSX: cell values only (cached formula results, no charts or macros).
- `ocr:true` on a PDF does not run OCR; it only adds a note.
- Limits: 40 MB per file, 50 pages by default, 30 s URL fetch timeout, 5,000 entries / 200 MB inflated for zip-based files.
