PDF Reader MCP Server
by serresident
README.md
# β‘ PDF Reader MCP Server
> **High-performance, ultra-low-token Model Context Protocol (MCP) server for LLMs and AI coding assistants.**
> Powered by C-accelerated [PyMuPDF](https://github.com/pymupdf/PyMuPDF) and [pymupdf4llm](https://github.com/pymupdf/pymupdf4llm) for pristine Markdown and table extraction.
---
## π― Why This MCP Server?
When AI agents (Claude, Gemini, Cursor, Antigravity) process PDFs through native chat uploads, the interface typically converts every page into high-resolution images (Vision) and raw OCR text:
* β οΈ **Massive Token Waste**: A 13-page technical document consumes **30,000β50,000 tokens**. A 370-page service manual easily exceeds **1,000,000+ tokens**, blowing past context limits.
* β οΈ **High Latency**: Uploading and parsing takes 15β40 seconds per interaction.
* β οΈ **Loss of Formatting**: Tables and multi-column layouts frequently lose their structure in raw text extraction.
### The Solution: On-Demand Introspection
`pdf-reader-mcp` allows AI agents to inspect, search, and extract **only the specific sections or pages they actually need**:
* π **2.1 seconds** to search an entire 373-page automotive technical manual.
* π **98β99% token reduction**: ~200 tokens per focused query instead of 50,000+.
* π **Table-Aware Markdown**: Preserves tables, headers, lists, and code blocks using `pymupdf4llm`.
---
## π οΈ Available MCP Tools
| Tool | Parameters | Description | Typical Token Cost |
|---|---|---|:---:|
| `pdf_info` | `file_path` | Returns document metadata: page count, size, title, author, encryption status. | ~30 tokens |
| `pdf_toc` | `file_path` | Extracts bookmarks and table of contents with hierarchy levels and target pages. | ~100 tokens |
| `pdf_search` | `file_path`, `query`, `max_results` (opt), `case_sensitive` (opt) | Lightning-fast full-text search across all pages with surrounding context snippets. | ~200 tokens |
| `pdf_read_page` | `file_path`, `page_number`, `as_markdown` (opt) | Extracts a single target page in clean Markdown or plain text. | ~150β300 tokens |
| `pdf_read_range` | `file_path`, `start_page`, `end_page`, `as_markdown` (opt) | Reads an inclusive range of pages formatted as Markdown with page break separators. | Proportional |
---
## π¦ Installation
### Prerequisites
* Python 3.10+ (tested on Python 3.11 and 3.12)
* Windows, macOS, or Linux
```bash
git clone https://github.com/serresident/pdf-reader-mcp.git
cd pdf-reader-mcp
pip install -r requirements.txt
```
---
## βοΈ Configuration
### 1. Antigravity / Gemini CLI (`mcp_config.json`)
Add to `~/.gemini/config/mcp_config.json`:
```json
{
"mcpServers": {
"pdf-reader-mcp": {
"command": "python",
"args": [
"C:\\Users\\erch\\ΠΠΎΠΉ Π΄ΠΈΡΠΊ\\1 for ai\\mcp\\pdf-reader-mcp\\pdf_reader_mcp.py"
]
}
}
}
```
### 2. Claude Desktop (`claude_desktop_config.json`)
On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"pdf-reader": {
"command": "python",
"args": [
"/path/to/pdf-reader-mcp/pdf_reader_mcp.py"
]
}
}
}
```
### 3. Cursor / Windsurf
Add as a **Command (stdio)** MCP server in the Settings β MCP panel:
* **Command**: `python`
* **Args**: `/absolute/path/to/pdf_reader_mcp.py`
---
## π How Agents Use It (Workflow)
```mermaid
flowchart TD
User["User asks question about PDF"] --> Agent["AI Agent"]
Agent -->|1. Check pages & size| Info["pdf_info"]
Agent -->|2. Check document structure| TOC["pdf_toc"]
Agent -->|3. Find keywords across 400 pages| Search["pdf_search"]
Search -->|Returns matching pages e.g. p. 36| Match["Target Pages Identified"]
Match -->|4. Read only relevant page| Read["pdf_read_page(36)"]
Read -->|Markdown + Tables| Agent
Agent --> Answer["Fast, accurate answer with zero context bloat!"]
```
---
## π§ͺ Testing
Run an end-to-end self-test directly in terminal:
```bash
python -c "
import subprocess, json
proc = subprocess.Popen(['python', 'pdf_reader_mcp.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, encoding='utf-8')
proc.stdin.write(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'initialize', 'params': {}}) + '\n')
proc.stdin.flush()
print('Server initialized:', proc.stdout.readline())
proc.terminate()
"
```
---
## π License
MIT License Β© 2026 [serresident](https://github.com/serresident).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues