Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
Your own AI agent, running 24/7 with QwikClaw logoYour own AI agent, running 24/7 with QwikClaw

QwikClaw sets up and runs an always-on OpenClaw agent for you. One click, no config files, no server setup.

Deploy now
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 47,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

An MCP server for PDF utilities including text extraction, metadata, merge/split/rotate, and PDF-to-image conversion.

README.md

FastMCP PDF Processing Server

An MCP server built with FastMCP (STDIO transport) offering PDF utilities: text extraction, metadata, merge/split/rotate, and PDF↔image conversion.

SPANISH VERSION [README.es.md]

Quick Start (Windows PowerShell)

python -m venv .venv
\.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env
python -m fastmcp_pdf_server

If installed as a package, you may also run: `` fastmcp-pdf-server ``

Quick Start (Linux/macOS)

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
python -m fastmcp_pdf_server

MCP Integration

  • Transport: STDIO. Do not print to stdout/stderr; logs go to file.
  • Server name/version: from config (server_name, server_version).
  • Tools are registered using @app.tool() and return structured outputs with a meta block containing operation_id and execution_ms.

Claude Desktop config example

Add to claude_desktop_config.json (Update with your own File System Path): ``json { "mcpServers": { "pdf-processor-server": { "command": "D:\\Github Projects\\mcp_pdf_server\\.venv\\Scripts\\python.exe", "args": [ "-m", "fastmcp_pdf_server" ], "env": { "MAX_FILE_SIZE_MB": "50", "TEMP_DIR": "D:\\Github Projects\\mcp_pdf_server\\temp_files", "LOG_LEVEL": "DEBUG", "LOG_FILE_PATH": "D:\\Github Projects\\mcp_pdf_server\\logs\\fastmcp_pdf_server.log", "SERVER_NAME": "pdf-processor-server", "SERVER_VERSION": "1.0.0", "PATH": "%PATH%;C:\\poppler-25.07.0\\Library\\bin" } } } ``

Note: If you update dependencies (e.g., we added requests for URL uploads), reinstall with: `` pip install -r requirements.txt ``

Claude Desktop config example (Linux)

Add to claude_desktop_config.json: ``json { "mcpServers": { "pdf-processor-server": { "command": "python3", "args": [ "-m", "fastmcp_pdf_server" ], "env": { "MAX_FILE_SIZE_MB": "50", "TEMP_DIR": "/home/you/dev/mcp_pdf_server/temp_files", "LOG_LEVEL": "DEBUG" } } } } ``

Claude Desktop config example (macOS)

Add to claude_desktop_config.json: ``json { "mcpServers": { "pdf-processor-server": { "command": "python3", "args": [ "-m", "fastmcp_pdf_server" ], "env": { "MAX_FILE_SIZE_MB": "50", "TEMP_DIR": "/Users/you/dev/mcp_pdf_server/temp_files", "LOG_LEVEL": "DEBUG" } } } } ``

Programmatic usage (Python)

import asyncio
from fastmcp import Client

async def main():
    client = Client(command="python", args=["-m", "fastmcp_pdf_server"])
    await client.start()
    try:
        info = await client.call_tool("server_info")
        print(info)
    finally:
        await client.close()

asyncio.run(main())

Exposed Tools (API)

All tools return structured data; many responses include meta.operation_id and meta.execution_ms. Some tools return lists (arrays). These are marked with FastMCP's x-fastmcp-wrap-result, so clients receive { "result": [...] } at the RPC layer.

MCP Tools Reference

Each tool lists: purpose, inputs, outputs, behavior, examples, and notes about errors and usage.

---

Utilities / Server

  • server_info()
  • Purpose: Return basic server info and configuration snapshot (non-secret).
  • Inputs: None
  • Returns: dict with keys:
  • name (str): server name from settings
  • version (str): server version from settings
  • max_file_size_mb (int): maximum configured file size in megabytes
  • temp_dir (str): absolute path to temporary files directory
  • log_file (str): absolute path to the log file
  • meta (dict): operation metadata: operation_id (hex), execution_ms (int)
  • Errors: none expected; if configuration missing, underlying access may raise exceptions.
  • Example:
  • Call: { "name": "server_info" }
  • Response: { "name": "mcp-pdf", "version": "1.0.0", "meta": { ... } }
  • list_temp_resources(content_type: Optional[str] = None, max_items: Optional[int] = 100) -> list[dict]
  • Purpose: List files currently in the server temp directory with optional filtering by content type.
  • Inputs:
  • content_type (optional str): MIME filter; supported examples: application/pdf, image/png, image/jpeg.
  • max_items (optional int): maximum number of entries to return (default 100). If set to null or 0, defaults to 100.
  • Returns: list of resource dicts (each):
  • path (str): absolute path to the temp file
  • size (int): size in bytes
  • created (str): creation timestamp (ISO or file-manager-specific format)
  • content_type (str): MIME type of resource
  • filename (str): filename only
  • extension (str): lowercased file extension (e.g. .pdf)
  • directory (str): parent directory of the file
  • Behavior: Cleans up expired temp files before listing. Result list is sliced to max_items.
  • Errors: Raises ValueError if internal listing fails.
  • Example call:
  • { "name": "list_temp_resources", "arguments": { "content_type": "application/pdf" } }
  • get_pdf_info(file_path: str) -> dict
  • Purpose: Read a PDF headers and basic info without extracting pages/text.
  • Inputs:
  • file_path (str): path to an existing file on disk (absolute or relative). Must be accessible to the server.
  • Returns: dict:
  • pages (int): number of pages
  • size (int): file size in bytes
  • version (str|None): PDF header/version info (if available)
  • encrypted (bool): whether the PDF is encrypted
  • meta (dict): operation_id, execution_ms
  • Errors:
  • Raises ValueError if file not found.
  • May raise other errors if the file is not a PDF or is corrupted.
  • get_resource_base64(file_path: str) -> dict
  • Purpose: Return base64-encoded contents of a file inside the server temp directory.
  • Inputs:
  • file_path (str): path; must be inside the configured temp directory. The function enforces this.
  • Returns: dict:
  • path (str): resolved path inside temp
  • base64 (str): Base64-encoded content of the file
  • meta (dict): operation metadata
  • Errors:
  • Raises ValueError if the path is outside temp or file missing.
  • Notes: Use this to fetch content for download via MCP where direct file transfers aren't available.

---

Uploads

  • upload_file(file: Any, filename: Optional[str] = None) -> dict
  • Purpose: Persist an uploaded file into the server temp directory.
  • Inputs:
  • file (Any): Accepts:
  • a full path string to a local file
  • a short filename that refers to a file already stored in temp
  • bytes or file-like object
  • dicts containing base64 and filename (will be saved to temp)
  • filename (Optional[str]): optional filename hint used when saving raw bytes.
  • Returns: dict:
  • path (str): absolute path to the saved file
  • filename (str): saved filename
  • directory (str): directory containing the file
  • meta (dict): operation metadata
  • Errors:
  • Raises ValueError with a descriptive message on failure (network, decoding, IO).
  • Example:
  • To upload base64: call upload_file with file = { "base64": "<...>", "filename": "my.pdf" }.
  • upload_file_base64(base64: str, filename: str) -> dict
  • Purpose: Upload raw Base64 content and persist to temp storage.
  • Inputs:
  • base64 (str): Base64 string
  • filename (str): filename to use when saving
  • Returns: dict:
  • path, filename, directory, size (int), meta
  • Errors: Raises ValueError on decoding or write errors.
  • upload_file_url(url: str, filename: Optional[str] = None) -> dict
  • Purpose: Download a remote file (HTTP/HTTPS) and save to temp storage.
  • Inputs:
  • url (str): direct URL to file
  • filename (Optional[str]): optional override filename
  • Returns: dict with path, filename, directory, meta.
  • Notes: Requires requests package to be available in the environment.

---

Text Extraction

  • extract_text(file: Any, encoding: Optional[str] = "utf-8") -> dict
  • Purpose: Extract all text from a PDF and return summary metrics.
  • Inputs:
  • file (Any): same resolver rules as upload_file (path, temp filename, bytes, base64 dict).
  • encoding (str|None): encoding used when returning text (default utf-8).
  • Returns: dict:
  • text (str): full extracted text
  • page_count (int): number of pages processed
  • char_count (int): number of characters in text
  • meta (dict): includes resolved_path pointing to saved temp file
  • Errors:
  • Raises ValueError with helpful hint explaining how to provide the file if extraction fails.
  • Example usage:
  • Upload a file with upload_file, then call extract_text with the returned path.
  • extract_text_by_page(file: Any, pages: Optional[List[int]] = None, page_range: Optional[str] = None, encoding: Optional[str] = "utf-8") -> list[dict]
  • Purpose: Extract text from specific pages or a page range.
  • Inputs:
  • file (Any): resolver rules as above
  • pages (Optional[List[int]]): list of 1-based page indices to extract (e.g., [1,3,5]).
  • page_range (Optional[str]): range expression like "1-3,5" (parser in utils.parsers will be used).
  • encoding (Optional[str]): text encoding
  • Returns: list of page result dicts; each dict typically contains:
  • page_number (int)
  • text (str)
  • char_count (int)
  • Behavior: If both pages and page_range are provided, pages takes precedence. The tool returns a list directly (framework wraps list results).
  • Errors: Raises ValueError on invalid pages or extraction failures.
  • extract_metadata(file: Any) -> dict
  • Purpose: Extract detailed PDF metadata (author, title, producer, creation/mod dates, custom metadata, etc.).
  • Inputs: file same as above.
  • Returns: dict containing metadata keys found in the PDF plus meta operation info.

---

Conversion

  • pdf_to_images(file_path: str, output_dir: str, format: str = "png", dpi: int = 150, pages: Optional[List[int]] = None) -> list[dict]
  • Purpose: Convert one or more PDF pages to image files.
  • Inputs:
  • file_path (str): path to the PDF on disk (absolute or temp path).
  • output_dir (str): directory where generated images will be written.
  • format (str): image format, e.g., png, jpeg.
  • dpi (int): resolution for conversion (default 150).
  • pages (Optional[List[int]]): list of 1-based pages to render; None for all pages.
  • Returns: list of dicts for each generated image:
  • path (str), page_number (int), size (int), format (str)
  • Notes: Implementation uses pdf2image and PIL; ensure dependencies and poppler are installed on the host.
  • images_to_pdf(image_paths: List[str], output_path: str, page_size: str = "A4", orientation: str = "portrait") -> dict
  • Purpose: Create a PDF document from multiple images.
  • Inputs:
  • image_paths (List[str]): list of image file paths in order
  • output_path (str): path for the generated PDF
  • page_size (str): e.g., A4, Letter (processor maps to physical sizes)
  • orientation (str): portrait or landscape
  • Returns: dict with success info and meta including operation timing.

---

PDF Manipulation

  • merge_pdfs(input_files: List[str], output_path: str) -> dict
  • Purpose: Merge multiple PDF files into a single PDF.
  • Inputs:
  • input_files (List[str]): file paths
  • output_path (str): destination path
  • Returns: dict with details (e.g., path) and meta.
  • split_pdf(file_path: str, split_ranges: List[Dict[str, Any]]) -> list[dict]
  • Purpose: Split a PDF into multiple files by page ranges.
  • Inputs:
  • file_path (str): source PDF
  • split_ranges (List[Dict]): each dict should describe start and end pages and optional filename.
  • Returns: list of generated files info dicts.
  • rotate_pages(file_path: str, rotations: List[Dict[str, int]], output_path: str) -> dict
  • Purpose: Rotate specific pages in a PDF and write to output_path.
  • Inputs:
  • file_path (str): source PDF
  • rotations (List[Dict]): each dict should include page (1-based) and degrees (e.g., 90, 180, 270).
  • output_path (str): target PDF path
  • Returns: dict with path and meta.

---

Notes:

  • All tools log an operation_id and execution time in ms in the returned meta object.
  • Tools that return lists set x-fastmcp-wrap-result=true for the framework so they are returned as bare lists.
  • Tools will raise ValueError for user-facing errors; internal exceptions are logged.
  • For file inputs, prefer uploading first via upload_file to ensure files are in the server temp directory.
  • page_range syntax uses utils.parsers.parse_page_range: e.g., "1-3,5,7-9".
  • If both pages and page_range are passed, pages takes precedence.
  • Image conversion requires Poppler (see below).

Example JSON: extract_text (simple)

  • Request arguments:
{
  "file": "C:/path/to/input.pdf",
  "encoding": "utf-8"
}
  • Response shape:
{
  "text": "... full extracted text ...",
  "page_count": 3,
  "char_count": 1234,
  "meta": { "operation_id": "<hex>", "execution_ms": 42 }
}

Uploading files (Claude Desktop and clients)

Claude may not automatically send binary file contents. Use one of these upload tools to persist a file to the server temp directory, then reference it by short filename in subsequent calls.

1) Upload a file (generic)

  • Tool: upload_file
  • Request:
{
  "name": "upload_file",
  "arguments": {
    "file": { "base64": "<BASE64_DATA>", "filename": "document.pdf" }
  }
}
  • Response contains filename and absolute path under the server temp_dir.

2) Upload a file as base64 (explicit schema)

  • Tool: upload_file_base64
  • Request:
{
  "name": "upload_file_base64",
  "arguments": { "base64": "<BASE64_DATA>", "filename": "document.pdf" }
}

3) Upload a file from URL (explicit schema)

  • Tool: upload_file_url
  • Request:
{
  "name": "upload_file_url",
  "arguments": { "url": "https://example.com/document.pdf", "filename": "document.pdf" }
}

4) Extract text using the saved short filename

  • Request:
{
  "name": "extract_text",
  "arguments": { "file": "document.pdf" }
}

Alternative: provide a URL to upload_file (requires requests installed): ``json { "name": "upload_file", "arguments": { "file": { "url": "https://example.com/document.pdf", "filename": "document.pdf" } } } ``

Manual option: run server_info to get temp_dir, copy your file into that directory, then call tools with the short filename.

Example JSON: merge_pdfs

  • Request arguments:
{
  "input_files": [
    "C:/path/a.pdf",
    "C:/path/b.pdf"
  ],
  "output_path": "C:/path/merged.pdf"
}
  • Response shape:
{
  "output_path": "C:/path/merged.pdf",
  "page_count": 10,
  "size": 456789,
  "meta": { "operation_id": "<hex>", "execution_ms": 87 }
}

Configuration

Configuration is loaded via pydantic-settings from .env and environment variables.

Env vars (case-insensitive):

  • MAX_FILE_SIZE_MB (int, default 50): Max file size for inputs.
  • LOG_LEVEL (str, default INFO): Logging level.
  • LOG_FILE_PATH (str, default logs/pdf-processor-server.log): Log file path.
  • TEMP_DIR (str, default temp_files): Working temp storage directory.
  • SERVER_NAME (str, default pdf-processor-server): Server name.
  • SERVER_VERSION (str, default 1.0.0): Server version.

Path helpers:

  • TEMP_DIR resolves to absolute settings.temp_path.
  • LOG_FILE_PATH resolves to absolute settings.log_path.

Storage & Security

  • Temp files are stored under TEMP_DIR and cleaned up automatically after 24h of inactivity.
  • ensure_within_temp(path) prevents reading files outside TEMP_DIR for base64 retrieval.
  • Validators enforce allowed extensions and size limits for PDFs and images.

Logging & Telemetry

  • Rotating logs at LOG_FILE_PATH (10MB x 5). No stdout/stderr prints.
  • Each tool returns meta.operation_id and meta.execution_ms for traceability.
  • Server banner and lifecycle logs are emitted by FastMCP at startup/shutdown.

Windows: Poppler for pdf2image

pdf2image requires Poppler binaries.

  • Download: https://github.com/oschwartz10612/poppler-windows/releases/
  • Extract, add poppler-*/Library/bin to your PATH.
  • Verify: pdftoppm -v prints a version. If not available, pdf_to_images tools will raise helpful errors.

Linux: Poppler for pdf2image

pdf2image requires Poppler binaries. Install via your package manager:

  • Debian/Ubuntu: sudo apt update && sudo apt install -y poppler-utils
  • Fedora: sudo dnf install -y poppler-utils
  • Arch: sudo pacman -S --noconfirm poppler
  • Verify: pdftoppm -v prints a version.

macOS: Poppler for pdf2image

Install Poppler with Homebrew: `` brew install poppler ` If Homebrew is in /opt/homebrew/bin (Apple Silicon), ensure your shell PATH includes it. Verify: pdftoppm -v`.

Developer Guide

Project layout

  • src/fastmcp_pdf_server/
  • main.py: Builds FastMCP app, registers tools, runs via STDIO.
  • config.py: Pydantic settings for env and paths.
  • utils/: Logger, validators, parsers.
  • services/: PDF and image operations, file manager.
  • tools/: Thin async wrappers exposing services as MCP tools.

Install & Run

python -m venv .venv
\.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env
python -m fastmcp_pdf_server

Linux/macOS: `` python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp .env.example .env python -m fastmcp_pdf_server ``

Tests

pytest -q

Conversion tests are skipped if Poppler (pdftoppm) is not found.

Troubleshooting

  • Startup hangs after banner: normal for STDIO mode (waiting for an MCP client).
  • pdf2image errors: ensure Poppler on PATH; retry shell after updating PATH.
  • ValueError: File not found or Invalid file extension: check inputs and validators.
  • Large files slow/timeout: reduce dpi, use page-range, or increase resources.

Performance Notes

  • Max file size is enforced; adjust MAX_FILE_SIZE_MB if needed.
  • Prefer page-scoped ops for large PDFs.
  • Lower dpi for faster PDF→image conversions.

Optional HTTP Mode (advanced)

FastMCP supports a streamable HTTP transport. This server defaults to STDIO. For experimentation, you can run an HTTP endpoint:

# run_http.py
import asyncio
from fastmcp_pdf_server.main import build_app

async def main():
  app = build_app()
  await app.run_http_async(host="127.0.0.1", port=8000, path="mcp")

asyncio.run(main())

Happy Coding!

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Files & Docs servers.