Merge, split, extract, rotate, reorder and stamp PDF files from chat: mark an invoice PAID, pull pages out of a scan, join a set of documents into one. Everything stays on your machine.
mcp-pdf demonstrates solid tool design with 8 well-named, verb-prefixed tools (pdf_info, pdf_merge, pdf_split, pdf_rotate, pdf_reorder, pdf_stamp, pdf_text, pdf_history). All tools have clear, substantive descriptions (100-200 chars typical) explaining what they do and when to use them. Input schemas are present and typed for all tools. However, output schemas are not documented, LLMs cannot predict return structure. Error handling is present but generic (fail() helper returns plain text errors without recovery guidance). Parameter descriptions are good but lack explicit constraints (e.g., pdf_stamp's font_size is clamped 1 - 1600 but not stated in the description; degrees param in pdf_rotate lacks enum constraint). The tool set is well-composed (each does one thing) and idempotent (safe to retry). Free-tier limits are documented in descriptions, which is helpful for agent planning.
List the operations this server performed on PDFs: merge, split, rotate, reorder, stamp, text. Newest first. Free tier: unlimited.
Call this tool to inspect a PDF: page count, page sizes in points/mm, paper name, metadata (title, author, producer, dates) and whether it is encrypted. Read-only, never modifies the file. Free tier: unlimited.
Merge multiple PDF files into one. Free tier: up to 5 files, 30 pages total.
Reorder pages of a PDF. Free tier: up to 30 pages.
Rotate pages of a PDF. Free tier: up to 30 pages.
Extract a subset of pages from a PDF and write them to a new file. Free tier: up to 30 pages.
Add a text stamp to pages of a PDF. Free tier: up to 30 pages.
Output schemas not documented. LLMs cannot predict return structure (e.g., what fields does pdf_info return? Is it a flat object or nested?). This forces agents to guess and risks parsing failures.
Parameter constraints not fully documented in descriptions. pdf_rotate's 'degrees' param accepts only 90, 180, 270, -90, -180, -270 but description lacks enum constraint. pdf_stamp's font_size is clamped 1 - 1600 but not stated in param description. LLMs may pass invalid values.
Error responses are generic text without recovery guidance. fail() helper returns plain 'Error: <message>' with no actionable next steps. Per pattern:recovery-guide, errors should suggest what to do next (e.g., 'File not found. Check the path or use pdf_info to list available files.').
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
Extract text from a PDF. Best-effort: reads the text operators on each page. Scanned PDFs (images with no text operators) come back empty. Custom or CID font encodings (most CJK, subset-embedded, or scanned-then-OCR'd documents) are reported as unreadable. Free tier: unlimited.
Page range syntax ('1-3,5,7-') is powerful but underdocumented. Description says 'Examples: "1", "1-3,5,7-", "-4"' but does not explain that '-4' means 'first 4 pages' or that '7-' means 'page 7 to end'. LLMs may misinterpret the syntax.
pdf_stamp's 'color' parameter accepts hex, 3-digit hex, or named colors, but description does not list all valid names. COLORS dict in code shows 9 names (red, green, blue, black, gray, grey, orange, purple, white) but param description only hints at them. LLMs may guess invalid names.