# PDFiTT PDFiTT is an open-source, self-hostable PDF MCP that renders Markdown into formatted PDFs and converts PDFs back to Markdown. It includes a browser UI, direct HTTP APIs, remote Streamable HTTP MCP endpoint, and portable agent skill. Primary repository: https://github.com/JamesFincher/PDFiTT Hosted MCP endpoint: https://pdf-i-tt.vercel.app/mcp Hosted direct APIs: https://pdf-i-tt.vercel.app/api/pdf, https://pdf-i-tt.vercel.app/api/markdown, https://pdf-i-tt.vercel.app/api/sign/autofill License: MIT ## LLM Quick Start If you are an AI coding agent, start here: 1. Read the repository `README.md` for project overview. 2. Read the repository `SELF_HOSTING.md` for install, deploy, and verification. 3. Read the repository `AGENTS.md` for repo-specific agent instructions. 4. Use `npm install`, `npm run lint`, `npm test`, and `npm run build` for local validation. 5. Use `codex mcp add pdfitt --url /mcp`, `claude mcp add --transport http pdfitt /mcp`, or the OpenCode JSON block in `README.md` to connect MCP clients. 6. Smoke both MCP scripts: `skills/pdfitt-markdown-pdf/scripts/render_pdf_via_mcp.mjs` and `skills/pdfitt-markdown-pdf/convert_pdf_via_mcp.mjs`. ## Public Interfaces MCP: - Path: `/mcp` - Transport: `streamable_http` - Protocol version: `2025-06-18` - Tools: `render_markdown_pdf`, `convert_pdf_to_markdown`, `get_setup_instructions`, `sign_preview_fields`, `sign_apply` - `convert_pdf_to_markdown` XOR: exactly one of `pdf_base64` or `source_url`. Empty both or both set is `{ code: -32602 }`. `document_type` includes `hybrid`. PdfConvertError / bad input is HTTP 400 `{ code: -32602, message: "..." }` with the request id. - Native fill-and-sign: `sign_preview_fields` maps Markdown to `signHere`, `signedAt`, `name`, and `checkbox`. `signedAt` stays empty until apply. `sign_apply` fills fields, stamps a typed PDFiTT signature, sets `signedAt` from the PDFiTT clock, and returns a real PDF. Footer: `Signed with PDFiTT`. No `DOCUSIGN_*` env. Direct API: - Path: `/api/pdf` - Method: `POST` - Inputs: `markdown`, `markdown_base64`, or `source_url` - Optional input: `filename` (keep a single `.pdf` extension; inner dots are preserved) - Output: `application/pdf` PDF→Markdown API: - Path: `/api/markdown` - Method: `POST` - Inputs: JSON `pdf_base64` XOR `source_url`, or multipart field `file` (`curl -F file=@scan.pdf`). The editor dropzone converts local files in the browser and does not POST `file`. - Optional: `ocr` (`auto`|`always`|`never`), `document_type` (`auto`|`text`|`image`|`hybrid`|`form`|`multi-column`), `filename` — JSON keys or multipart fields - Exactly one of `file`, `pdf_base64`, or `source_url`. Never 404/415 for a valid JSON or multipart POST. - Hosted Vercel Hobby POST bodies cap at about 4.5MB. `file` / `pdf_base64` 413 above that. Large agent uploads must use `source_url`. - Output JSON: `markdown`, `page_count`, `ocr_used`, `warnings`, `filename`, `document_class`, `page_classes`, `ocr_confidence` Native sign API: - Path: `/api/sign/autofill` - Method: `POST` - Input: `{ markdown }` - Auth: none. Convert stays public. - Output: `{ fields, warnings }` - Path: `/api/sign/apply` - Method: `POST` - Auth: Clerk bearer token. No token → 401. Returns `application/pdf`. ## Self-Hosting Contract To self-host: 1. Fork or clone https://github.com/JamesFincher/PDFiTT. 2. Install dependencies with `npm install`. 3. Run locally with `npm run dev`. 4. Deploy to Vercel. 5. Set `PDFITT_PUBLIC_URL` to the deployed origin, without a trailing slash. 6. Optionally set `PDFITT_ALLOWED_ORIGINS` to a comma-separated browser-origin allowlist. MCP clients that send `MCP-Protocol-Version` are not blocked by a foreign Origin. 7. Optional Clerk: `VITE_CLERK_PUBLISHABLE_KEY` + `CLERK_SECRET_KEY` enable `/sign-in`, `/dashboard`, and `/account`. Convert stays public. Vercel SPA rewrites those paths to `/index.html`. 8. Verify `/mcp`, `/api/pdf`, `/api/markdown`, `/sign-in`, `/dashboard`, and the browser UI. When configuring a fork, replace `https://pdf-i-tt.vercel.app` with the fork's deployed origin. ## Security Notes - Do not send private local filesystem paths to the remote MCP server. - Read local Markdown or PDF files locally, then send `markdown` or `pdf_base64`. - `source_url` must be public and must use `http` or `https`. Local paths and `file:` URLs are rejected. - Browser editor convert: 300,000,000 bytes and 500 pages. Node/API/MCP: 8,000,000 bytes (`source_url` fetch 40,000,000) and the first 15 pages. Hosted POST `/api/markdown` and MCP `pdf_base64` still 413 around 4.5MB; use `source_url` or the browser dropzone. - Image OCR uses tesseract.js WASM (`tesseract.js` + `tesseract.js-core`) and embedded page images (`paintImageXObject`). Hosted `POST /api/markdown ocr:auto` on golden-tiny is HTTP 200 in about 2s (in-process WASM) — Hobby OCR is not dead. Worker, SIMD-LSTM core, and `@tesseract.js-data/eng` are bundled. The editor dropzone still converts local files in-browser and serves core/lang from `/ocr/` with canonical names. Text-layer pages are never rasterized. No native canvas, sharp, or native Tesseract. Arbitrary camera photos may return `low-ocr-confidence`. - Raw script/style HTML is stripped from PDF conversion. - Browser preview HTML is sanitized with DOMPurify.