PDF Processor Server
An MCP server for PDF utilities including text extraction, metadata, merge/split/rotate, and PDF-to-image conversion.
README
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 ametablock containingoperation_idandexecution_ms.
Claude Desktop config example
Add to claude_desktop_config.json (Update with your own File System Path):
{
"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:
{
"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:
{
"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 settingsversion(str): server version from settingsmax_file_size_mb(int): maximum configured file size in megabytestemp_dir(str): absolute path to temporary files directorylog_file(str): absolute path to the log filemeta(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": { ... } }
- Call:
-
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 filesize(int): size in bytescreated(str): creation timestamp (ISO or file-manager-specific format)content_type(str): MIME type of resourcefilename(str): filename onlyextension(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
ValueErrorif 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 pagessize(int): file size in bytesversion(str|None): PDF header/version info (if available)encrypted(bool): whether the PDF is encryptedmeta(dict):operation_id,execution_ms
- Errors:
- Raises
ValueErrorif file not found. - May raise other errors if the file is not a PDF or is corrupted.
- Raises
-
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 tempbase64(str): Base64-encoded content of the filemeta(dict): operation metadata
- Errors:
- Raises
ValueErrorif the path is outside temp or file missing.
- Raises
- 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
base64andfilename(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 filefilename(str): saved filenamedirectory(str): directory containing the filemeta(dict): operation metadata
- Errors:
- Raises
ValueErrorwith a descriptive message on failure (network, decoding, IO).
- Raises
- Example:
- To upload base64: call
upload_filewithfile={ "base64": "<...>", "filename": "my.pdf" }.
- To upload base64: call
-
upload_file_base64(base64: str, filename: str) -> dict- Purpose: Upload raw Base64 content and persist to temp storage.
- Inputs:
base64(str): Base64 stringfilename(str): filename to use when saving
- Returns: dict:
path,filename,directory,size(int),meta
- Errors: Raises
ValueErroron 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 filefilename(Optional[str]): optional override filename
- Returns: dict with
path,filename,directory,meta. - Notes: Requires
requestspackage 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 asupload_file(path, temp filename, bytes, base64 dict).encoding(str|None): encoding used when returning text (defaultutf-8).
- Returns: dict:
text(str): full extracted textpage_count(int): number of pages processedchar_count(int): number of characters intextmeta(dict): includesresolved_pathpointing to saved temp file
- Errors:
- Raises
ValueErrorwith helpful hint explaining how to provide the file if extraction fails.
- Raises
- Example usage:
- Upload a file with
upload_file, then callextract_textwith the returnedpath.
- Upload a file with
-
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 abovepages(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 inutils.parserswill 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
pagesandpage_rangeare provided,pagestakes precedence. The tool returns a list directly (framework wraps list results). - Errors: Raises
ValueErroron 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:
filesame as above. - Returns: dict containing metadata keys found in the PDF plus
metaoperation 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;Nonefor all pages.
- Returns: list of dicts for each generated image:
path(str),page_number(int),size(int),format(str)
- Notes: Implementation uses
pdf2imageand 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 orderoutput_path(str): path for the generated PDFpage_size(str): e.g.,A4,Letter(processor maps to physical sizes)orientation(str):portraitorlandscape
- Returns: dict with success info and
metaincluding 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 pathsoutput_path(str): destination path
- Returns: dict with details (e.g.,
path) andmeta.
-
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 PDFsplit_ranges(List[Dict]): each dict should describestartandendpages and optionalfilename.
- 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 PDFrotations(List[Dict]): each dict should includepage(1-based) anddegrees(e.g., 90, 180, 270).output_path(str): target PDF path
- Returns: dict with
pathandmeta.
- Purpose: Rotate specific pages in a PDF and write to
Notes:
- All tools log an
operation_idand execution time in ms in the returnedmetaobject. - Tools that return lists set
x-fastmcp-wrap-result=truefor the framework so they are returned as bare lists. - Tools will raise
ValueErrorfor user-facing errors; internal exceptions are logged. - For file inputs, prefer uploading first via
upload_fileto ensure files are in the server temp directory. page_rangesyntax usesutils.parsers.parse_page_range: e.g.,"1-3,5,7-9".- If both
pagesandpage_rangeare passed,pagestakes 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.
- Upload a file (generic)
- Tool:
upload_file - Request:
{
"name": "upload_file",
"arguments": {
"file": { "base64": "<BASE64_DATA>", "filename": "document.pdf" }
}
}
- Response contains
filenameand absolutepathunder the servertemp_dir.
- Upload a file as base64 (explicit schema)
- Tool:
upload_file_base64 - Request:
{
"name": "upload_file_base64",
"arguments": { "base64": "<BASE64_DATA>", "filename": "document.pdf" }
}
- 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" }
}
- 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):
{
"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, defaultINFO): Logging level.LOG_FILE_PATH(str, defaultlogs/pdf-processor-server.log): Log file path.TEMP_DIR(str, defaulttemp_files): Working temp storage directory.SERVER_NAME(str, defaultpdf-processor-server): Server name.SERVER_VERSION(str, default1.0.0): Server version.
Path helpers:
TEMP_DIRresolves to absolutesettings.temp_path.LOG_FILE_PATHresolves to absolutesettings.log_path.
Storage & Security
- Temp files are stored under
TEMP_DIRand cleaned up automatically after 24h of inactivity. ensure_within_temp(path)prevents reading files outsideTEMP_DIRfor 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_idandmeta.execution_msfor 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/binto yourPATH. - Verify:
pdftoppm -vprints a version. If not available,pdf_to_imagestools 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 -vprints 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).
pdf2imageerrors: ensure Poppler on PATH; retry shell after updating PATH.ValueError: File not foundorInvalid 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_MBif needed. - Prefer page-scoped ops for large PDFs.
- Lower
dpifor 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!
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。