Skip to content
Conduit

mcp server

PDF-MCP-Server

A high-performance MCP (Model Context Protocol) server for PDF processing, built in Rust/C++.

paradyno/PDF-MCP-Servercommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 22, 2026

📄 PDF MCP Server

A high-performance MCP server for PDF processing, built in Rust.

License CI codecov

Give your AI agents powerful PDF capabilities — extract text, search, split, merge, encrypt, and more. All dependencies are Apache 2.0 licensed, keeping your project clean and permissive.

✨ Features

CategoryTools
📖 Readingextract_text · extract_metadata · extract_outline · extract_annotations · extract_links · extract_form_fields
🔍 Search & Discoverysearch · list_pdfs · get_page_info · summarize_structure
🖼️ MediaImage extraction (via extract_text) · convert_page_to_image
✂️ Manipulationsplit_pdf · merge_pdfs · compress_pdf · fill_form
🔒 Securityprotect_pdf · unprotect_pdf · Password-protected PDF support · Path sandboxing · SSRF protection
📦 ResourcesExpose PDFs as MCP Resources for direct client access
⚡ PerformanceBatch processing · LRU caching (with byte budget) · Operation chaining via cache keys

🚀 Installation

npm (Recommended)

npm install -g @paradyno/pdf-mcp-server

Pre-built Binaries

Download from GitHub Releases:

Platformx86_64ARM64
🐧 Linuxpdf-mcp-server-linux-x64pdf-mcp-server-linux-arm64
🍎 macOSpdf-mcp-server-darwin-x64pdf-mcp-server-darwin-arm64
🪟 Windowspdf-mcp-server-windows-x64.exe—

From Source

cargo install --git https://github.com/paradyno/pdf-mcp-server

⚙️ Configuration

Claude Desktop

Add to your claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "pdf": {
      "command": "npx",
      "args": ["@paradyno/pdf-mcp-server"]
    }
  }
}

Claude Code

claude mcp add pdf -- npx @paradyno/pdf-mcp-server

VS Code

{
  "mcp.servers": {
    "pdf": {
      "command": "npx",
      "args": ["@paradyno/pdf-mcp-server"]
    }
  }
}

🛠️ Tools

Source Types

All tools accept PDF sources in multiple formats:

{ "path": "/documents/file.pdf" }
{ "base64": "JVBERi0xLjQK..." }
{ "url": "https://example.com/document.pdf" }
{ "cache_key": "abc123" }

📖 extract_text

Extract text content with LLM-optimized formatting (paragraph detection, multi-column reordering, watermark removal).

{
  "sources": [{ "path": "/documents/report.pdf" }],
  "pages": "1-10",
  "include_metadata": true
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
pagesstringNoallPage selection (e.g., "1-5,10,15-20")
include_metadatabooleanNotrueInclude PDF metadata
include_imagesbooleanNofalseInclude extracted images (base64 PNG)
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

📖 extract_outline

Extract PDF bookmarks / table of contents.

{
  "sources": [{ "path": "/documents/book.pdf" }]
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

Response:

{
  "results": [{
    "source": "/documents/book.pdf",
    "outline": [
      {
        "title": "Chapter 1: Introduction",
        "page": 1,
        "children": [
          { "title": "1.1 Background", "page": 3, "children": [] }
        ]
      }
    ]
  }]
}

📖 extract_metadata

Extract PDF metadata (author, title, dates, etc.) without loading full content.

{
  "sources": [{ "path": "/documents/report.pdf" }]
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

📖 extract_annotations

Extract highlights, comments, underlines, and other annotations.

{
  "sources": [{ "path": "/documents/report.pdf" }],
  "annotation_types": ["highlight", "text"],
  "pages": "1-5"
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
annotation_typesarrayNoallFilter by types (highlight, underline, text, etc.)
pagesstringNoallPage selection
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

📖 extract_links

Extract hyperlinks and internal page navigation links.

{
  "sources": [{ "path": "/documents/paper.pdf" }],
  "pages": "1-10"
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
pagesstringNoallPage selection
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

Response:

{
  "results": [{
    "source": "/documents/paper.pdf",
    "links": [
      { "page": 1, "url": "https://example.com", "text": "Click here" },
      { "page": 3, "dest_page": 10, "text": "See Chapter 5" }
    ],
    "total_count": 2
  }]
}

📖 extract_form_fields

Read form field names, types, current values, and properties from PDF forms.

{
  "sources": [{ "path": "/documents/form.pdf" }],
  "pages": "1"
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
pagesstringNoallPage selection
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

Response:

{
  "results": [{
    "source": "/documents/form.pdf",
    "fields": [
      {
        "page": 1,
        "name": "full_name",
        "field_type": "text",
        "value": "John Doe",
        "is_read_only": false,
        "is_required": true,
        "properties": { "is_multiline": false, "is_password": false }
      },
      {
        "page": 1,
        "name": "agree_terms",
        "field_type": "checkbox",
        "is_checked": true,
        "is_read_only": false,
        "is_required": false,
        "properties": {}
      }
    ],
    "total_fields": 2
  }]
}

🖼️ convert_page_to_image

Render PDF pages as PNG images (base64). Enables Vision LLMs to understand visual layouts, charts, and diagrams.

{
  "sources": [{ "path": "/documents/chart.pdf" }],
  "pages": "1-3",
  "width": 1200
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
pagesstringNoallPage selection
widthintegerNo1200Target width in pixels
heightintegerNo—Target height in pixels
scalefloatNo—Scale factor (overrides width/height)
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

Response:

{
  "results": [{
    "source": "/documents/chart.pdf",
    "pages": [
      {
        "page": 1,
        "width": 1200,
        "height": 1553,
        "data_base64": "iVBORw0KGgo...",
        "mime_type": "image/png"
      }
    ]
  }]
}

🔍 search

Full-text search within PDFs with surrounding context.

{
  "sources": [{ "path": "/documents/manual.pdf" }],
  "query": "error handling",
  "context_chars": 100
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
querystringYes—Search query
case_sensitivebooleanNofalseCase-sensitive search
max_resultsintegerNo100Maximum results to return
context_charsintegerNo50Characters of context around match
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

🔍 get_page_info

Get page dimensions, word/char counts, token estimates, and file sizes. Useful for planning LLM context usage.

{
  "sources": [{ "path": "/documents/report.pdf" }]
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching
skip_file_sizesbooleanNofalseSkip file size calculation (faster)

Response:

{
  "results": [{
    "source": "/documents/report.pdf",
    "pages": [{
      "page": 1,
      "width": 612.0, "height": 792.0,
      "rotation": 0, "orientation": "portrait",
      "char_count": 2500, "word_count": 450,
      "estimated_token_count": 625,
      "file_size": 102400
    }],
    "total_pages": 10,
    "total_chars": 25000,
    "total_words": 4500,
    "total_estimated_token_count": 6250
  }]
}

Note: Token counts are model-dependent approximations (~4 chars/token for Latin, ~2 tokens/char for CJK). Use as rough guidance only.

🔍 summarize_structure

One-call comprehensive overview of a PDF's structure. Helps LLMs decide how to process a document.

{
  "sources": [{ "path": "/documents/report.pdf" }]
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources
passwordstringNo—PDF password if encrypted
cachebooleanNofalseEnable caching

Response:

{
  "results": [{
    "source": "/documents/report.pdf",
    "page_count": 25,
    "file_size": 1048576,
    "metadata": { "title": "Annual Report", "author": "Acme Corp" },
    "has_outline": true,
    "outline_items": 12,
    "total_chars": 50000,
    "total_words": 9000,
    "total_estimated_tokens": 12500,
    "pages": [
      { "page": 1, "width": 612.0, "height": 792.0, "char_count": 2000, "word_count": 360, "has_images": true, "has_links": false, "has_annotations": false }
    ],
    "total_images": 5,
    "total_links": 3,
    "total_annotations": 2,
    "has_form": false,
    "form_field_count": 0,
    "form_field_types": {},
    "is_encrypted": false
  }]
}

🔍 list_pdfs

Discover PDF files in a directory with optional filtering.

{
  "directory": "/documents",
  "recursive": true,
  "pattern": "invoice*.pdf"
}
ParameterTypeRequiredDefaultDescription
directorystringYes—Directory to search
recursivebooleanNofalseSearch subdirectories
patternstringNo—Filename pattern (e.g., "report*.pdf")

✂️ split_pdf

Extract specific pages from a PDF to create a new PDF.

{
  "source": { "path": "/documents/book.pdf" },
  "pages": "1-10,15,20-z",
  "output_path": "/output/excerpt.pdf"
}
ParameterTypeRequiredDefaultDescription
sourceobjectYes—PDF source
pagesstringYes—Page range (see syntax below)
output_pathstringNo—Save output to file
passwordstringNo—PDF password if encrypted

Page Range Syntax:

SyntaxDescription
1-5Pages 1 through 5
1,3,5Specific pages
zLast page
r1Last page (reverse)
5-zPage 5 to end
z-1All pages reversed
1-z:oddOdd pages only
1-z:evenEven pages only
1-10,x5Pages 1–10 except page 5

✂️ merge_pdfs

Merge multiple PDFs into a single file.

{
  "sources": [
    { "path": "/documents/chapter1.pdf" },
    { "path": "/documents/chapter2.pdf" }
  ],
  "output_path": "/output/complete-book.pdf"
}
ParameterTypeRequiredDefaultDescription
sourcesarrayYes—PDF sources to merge (in order)
output_pathstringNo—Save output to file

✂️ compress_pdf

Reduce PDF file size using stream optimization, object deduplication, and compression.

{
  "source": { "path": "/documents/large-report.pdf" },
  "compression_level": 9,
  "output_path": "/output/compressed.pdf"
}
ParameterTypeRequiredDefaultDescription
sourceobjectYes—PDF source
object_streamsstringNo"generate""generate" (best) · "preserve" · "disable"
compression_levelintegerNo91–9 (higher = better compression)
output_pathstringNo—Save output to file
passwordstringNo—PDF password if encrypted

Response:

{
  "results": [{
    "source": "/documents/large-report.pdf",
    "original_size": 5242880,
    "compressed_size": 2097152,
    "compression_ratio": 0.4,
    "bytes_saved": 3145728
  }]
}

✂️ fill_form

Write values into existing PDF form fields and produce a new PDF.

{
  "source": { "path": "/documents/form.pdf" },
  "field_values": [
    { "name": "full_name", "value": "Jane Smith" },
    { "name": "agree_terms", "checked": true }
  ],
  "output_path": "/output/filled-form.pdf"
}
ParameterTypeRequiredDefaultDescription
sourceobjectYes—PDF source
field_valuesarrayYes—Fields to fill (see below)
output_pathstringNo—Save output to file
passwordstringNo—PDF password if encrypted

Field value format:

FieldTypeDescription
namestringField name (use extract_form_fields to discover names)
valuestringText value (for text fields)
checkedbooleanChecked state (for checkbox/radio fields)

Supported field types: Text fields, checkboxes, radio buttons. ComboBox/ListBox selection is read-only.

🔒 protect_pdf

Add password protection using 256-bit AES encryption.

{
  "source": { "path": "/documents/confidential.pdf" },
  "user_password": "secret123",
  "allow_print": "none",
  "allow_copy": false
}
ParameterTypeRequiredDefaultDescription
sourceobjectYes—PDF source
user_passwordstringYes—Password to open the PDF
owner_passwordstringNouser_passwordPassword to change permissions
allow_printstringNo"full""full" · "low" · "none"
allow_copybooleanNotrueAllow copying text/images
allow_modifybooleanNotrueAllow modifying the document
output_pathstringNo—Save output to file
passwordstringNo—Password for source PDF if encrypted

🔓 unprotect_pdf

Remove password protection from an encrypted PDF.

{
  "source": { "path": "/documents/protected.pdf" },
  "password": "secret123",
  "output_path": "/output/unprotected.pdf"
}
ParameterTypeRequiredDefaultDescription
sourceobjectYes—PDF source
passwordstringYes—Password for the encrypted PDF
output_pathstringNo—Save output to file

📦 MCP Resources

Expose PDFs from configured directories as MCP Resources for direct client discovery and reading.

Enabling Resources

# Command line
pdf-mcp-server --resource-dir /documents --resource-dir /data/pdfs

# Short form
pdf-mcp-server -r /documents -r /data/pdfs

# Environment variable (path-separated: colon on Unix, semicolon on Windows)
PDF_RESOURCE_DIRS=/documents:/data/pdfs pdf-mcp-server

Claude Desktop with resources:

{
  "mcpServers": {
    "pdf": {
      "command": "npx",
      "args": ["@paradyno/pdf-mcp-server", "--resource-dir", "/documents"],
      "env": {
        "PDF_RESOURCE_DIRS": "/data/pdfs:/shared/documents"
      }
    }
  }
}

Both methods can be combined — command line arguments are added to environment variable paths.

Resource URIs

PDFs are exposed with file:// URIs:

file:///documents/report.pdf
file:///documents/2024/invoice.pdf

Operations

  • resources/list — Returns all PDFs with URI, name, MIME type, size, and description
  • resources/read — Returns extracted text content, formatted for LLM consumption

Resources vs Tools vs Caching

FeaturePurposeUse Case
ResourcesPassive file discoveryBrowse and preview available PDFs
ToolsActive PDF processingExtract, search, manipulate PDFs
CacheRefTool chainingPass output between operations

🔒 Security

Path Sandboxing

When --resource-dir is specified, all file operations (reads and writes) are sandboxed to the configured directories. Path traversal attempts (e.g., ../../etc/passwd) are blocked via canonicalize().

Without --resource-dir, all paths are allowed (backward compatible).

SSRF Protection

URL sources are checked for SSRF by default. URLs that resolve to private or reserved IP addresses are blocked:

  • Loopback (127.0.0.0/8, ::1)
  • Private (10/8, 172.16/12, 192.168/16, fc00::/7)
  • Link-local (169.254/16, fe80::/10) — blocks cloud metadata endpoints
  • CGNAT (100.64/10)
  • Broadcast, unspecified

Use --allow-private-urls or PDF_ALLOW_PRIVATE_URLS=1 to disable this check (e.g., for local development).

Resource Limits

LimitDefaultCLI FlagEnv Var
URL download size100 MB--max-download-sizePDF_MAX_DOWNLOAD_BYTES
Cache total bytes512 MB--cache-max-bytesPDF_CACHE_MAX_BYTES
Cache entries100--cache-max-entriesPDF_CACHE_MAX_ENTRIES
Image scale factor10.0--max-image-scalePDF_MAX_IMAGE_SCALE
Image pixel area100M--max-image-pixelsPDF_MAX_IMAGE_PIXELS

🔗 Caching

When cache: true is specified, the server returns a cache_key for use in subsequent requests:

// Step 1: Extract with caching
{ "sources": [{ "path": "/documents/large.pdf" }], "cache": true }

// Step 2: Use cache_key from response
{ "sources": [{ "cache_key": "a1b2c3d4" }], "pages": "50-60" }

🏗️ Architecture

block-beta
  columns 1
  block:server["MCP Server (rmcp)"]
    columns 3
    extract_text search split_pdf
  end
  block:common["Common Layer"]
    columns 3
    Cache["Cache Manager"] Source["Source Resolver"] Batch["Batch Executor"]
  end
  block:pdf["PDF Processing"]
    columns 2
    PDFium["pdfium-render\n(reading)"] qpdf["qpdf FFI\n(manipulation)"]
  end

  server --> common --> pdf

⚡ Performance

Benchmarked with a 14-page technical paper (tracemonkey.pdf, ~1 MB) on Docker (Apple Silicon):

OperationTimeWhat it means
Extract text (14 pages)170 msProcess ~80 documents per minute
Metadata only0.26 ms~4,000 documents per second
Search0.01 msInstant results on extracted text
100 files batch4.8 s~21 documents per second

Key takeaways

  • Fast enough for interactive use — Text extraction completes in under 200ms
  • Metadata is nearly instant — Use extract_metadata or summarize_structure to quickly assess documents before full processing
  • Search is blazing fast — Once text is extracted, searching is essentially free
  • Batch processing scales linearly — No significant overhead when processing many files

Run benchmarks yourself:

docker compose --profile dev run --rm bench

🧑‍💻 Development

# Build
docker compose --profile dev run --rm dev cargo build

# Run tests
docker compose --profile dev run --rm test

# Run tests with coverage
docker compose --profile dev run --rm coverage

# Format code
docker compose --profile dev run --rm dev cargo fmt --all

# Lint
docker compose --profile dev run --rm clippy

# Performance benchmarks
docker compose --profile dev run --rm bench

# Build production image (~120MB)
docker compose --profile prod build production

# Clean up
docker compose --profile dev down --rmi local

Requires PDFium installed locally. Download from pdfium-binaries and set PDFIUM_PATH.

cargo build --release
cargo test
cargo bench
cargo llvm-cov --html
src/
├── main.rs              # Entry point, CLI args
├── lib.rs               # Library root
├── server.rs            # MCP server & tool handlers
├── error.rs             # Error types
├── pdf/
│   ├── reader.rs        # PDFium wrapper (text, metadata, outline)
│   ├── annotations.rs   # Annotation extraction
│   ├── images.rs        # Image extraction
│   └── qpdf.rs          # qpdf FFI (split, merge, encrypt)
└── source/
    ├── resolver.rs      # Path/URL/Base64 resolution
    └── cache.rs         # LRU caching layer

🗺️ Roadmap

Phase 1: Core Reading ✅

extract_text · extract_outline · search · extract_metadata · extract_annotations · Image extraction · Batch processing · Caching

Phase 2: PDF Manipulation ✅

split_pdf · merge_pdfs · protect_pdf · unprotect_pdf · compress_pdf · extract_links · get_page_info

Phase 2.5: LLM-Optimized Text ✅

Dynamic thresholds · Paragraph detection · Multi-column layout · Watermark removal

Phase 2.6: Discovery & Resources ✅

list_pdfs · MCP Resources · Resource directory configuration

Phase 2.7: Vision & Forms ✅

convert_page_to_image · extract_form_fields · fill_form · summarize_structure

Phase 3: Advanced Features (Planned)

  • rotate_pages — Rotate specific pages
  • extract_tables — Structured table extraction
  • add_watermark — Text/image watermarks
  • linearize_pdf — Web optimization
  • OCR support · PDF/A validation · Digital signature verification
  • Large file upload — MCP lacks a standard API for uploading large files (>20MB). Discussed in #1197, #1220, #1659.
  • Chunked file transfer — No standard mechanism exists yet.

Current workarounds: shared filesystem (path), object storage with pre-signed URLs (url), or base64 encoding.

These provide limited value for LLM use cases:

  • Hyphenation merging — LLMs understand hyphenated words
  • Fixed-pitch mode — Limited use cases
  • Bounding box output — LLMs don't need coordinates
  • Invisible text removal — Not supported by pdfium-render API

📄 License

Apache License 2.0

🙏 Acknowledgments

  • PDFium — PDF rendering engine (Apache 2.0)
  • pdfium-render — Rust PDFium bindings (Apache 2.0)
  • qpdf — PDF transformation library, vendored via FFI (Apache 2.0)
  • rmcp — Rust MCP SDK
↑↓ navigate openesc close