# pdf-triage-mcp
[](https://www.npmjs.com/package/pdf-triage-mcp)
[](https://github.com/vishalmeena2211/pdf-triage-mcp/actions/workflows/ci.yml)
[](https://registry.modelcontextprotocol.io)
[](LICENSE)
An MCP server that lets any AI tool read local PDFs — **without uploading them, without an OCR bill, and without silently handing back garbage.**
Built on [`@firecrawl/pdf-inspector`](https://github.com/firecrawl/pdf-inspector) (Rust, no ML models, no external services).
```
┌─ pdf_classify ──→ text_based · 0.98 · 12 pages · 0 need OCR ~20ms
├─ pdf_search ──→ "invoice total" found on p4, p9 ~150ms
├─ pdf_extract ──→ clean Markdown, truncated to your budget ~150ms
└─ pdf_tables ──→ just the pipe tables, no prose ~150ms
```
---
## Table of contents
- [Why this exists](#why-this-exists)
- [Install](#install)
- [Connect it to your AI tool](#connect-it-to-your-ai-tool) — 12 clients
- [Tools](#tools)
- [Configuration](#configuration)
- [Engine fallback](#engine-fallback)
- [Known limitations](#known-limitations)
- [Development](#development)
---
## Why this exists
Most PDF tooling has the same failure mode: **it returns confident text regardless of whether extraction actually worked.** Broken CID fonts, substitution-cipher encodings, scanned pages with no text layer — you get plausible-looking output and find out downstream, if at all.
`pdf-inspector` is unusually good at *knowing* when it failed. It emits `U+FFFD` rather than guessing at an unmapped CID, runs substitution-cipher detection over its own output, and reclassifies a document as scanned when extracted text drops below 50% alphanumeric. But it stops at reporting those findings on a result object — and most wrappers throw them away.
**This server acts on them.** Every response carries the trust signals, above the content, where the model reads them first:
```markdown
> [!WARNING] ENCODING ISSUES DETECTED. The text layer decoded to suspicious
> output — typically a garbled CID font or a substitution-cipher encoding
> where letter frequencies match natural language but the letters themselves
> are wrong. Treat all extracted text here as unreliable and prefer OCR.
---
# Quarterly Report
...
```
Three design rules follow:
1. **Classify before extracting.** `pdf_classify` costs ~20ms and tells you whether extraction is worth attempting at all.
2. **Bound every output.** A 300-page PDF is easily 500k tokens. Everything truncates by default and tells you how to page through instead.
3. **Confine every path.** A model that has just read an untrusted document must not be talkable into reading `~/.ssh/id_rsa`. Enforced in code, not left to the model's judgement.
---
## Install
**Nothing to install.** Every config below runs the published package straight from npm:
```
npx -y pdf-triage-mcp --root /path/to/your/documents
```
Your MCP client runs that for you — you only need to paste the config. Confirm it works first:
```bash
npx -y pdf-triage-mcp --version
```
**Requires Node 20+.** Available on npm as [`pdf-triage-mcp`](https://www.npmjs.com/package/pdf-triage-mcp) and in the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.vishalmeena2211/pdf-triage-mcp`.
From source instead (for development)
```bash
git clone https://github.com/vishalmeena2211/pdf-triage-mcp.git
cd pdf-triage-mcp
npm install
npm run build
node dist/index.js --root ~/Documents
```
Then substitute `"command": "node", "args": ["/absolute/path/to/dist/index.js", ...]` for the `npx` invocation in any config below.
1. Claude Desktop
**Config file**
| OS | Path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
**Verify:** Fully quit and relaunch Claude Desktop (not just close the window). A tools icon appears near the chat input — click it and confirm the four `pdf_*` tools are listed.
Logs: `~/Library/Logs/Claude/mcp*.log` (macOS), `%APPDATA%\Claude\logs\mcp*.log` (Windows).
[Docs](https://modelcontextprotocol.io/quickstart/user)
2. Claude Code
**CLI — the easiest route.** The `--` separator is mandatory; everything after it is the server command.
```bash
# Just you, this project (default)
claude mcp add pdf-triage -- npx -y pdf-triage-mcp --root /Users/me/Documents
# Just you, every project
claude mcp add --scope user pdf-triage -- npx -y pdf-triage-mcp --root /Users/me/Documents
# Shared with your team, writes .mcp.json to the repo
claude mcp add --scope project pdf-triage -- npx -y pdf-triage-mcp --root /Users/me/Documents
```
**Or edit `.mcp.json` at the project root directly:**
```json
{
"mcpServers": {
"pdf-triage": {
"type": "stdio",
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
| Scope | Stored in | Shared |
|---|---|---|
| `local` (default) | `~/.claude.json`, under this project | No |
| `user` | `~/.claude.json`, top level | No |
| `project` | `.mcp.json` in repo root | Yes, via git |
**Verify:** `claude mcp list` → look for `✔ Connected`. Project-scoped servers need approval on first use — run `/mcp` inside a session.
[Docs](https://code.claude.com/docs/en/mcp)
3. Cursor
**Config file:** `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global). Project wins on conflict.
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
**Verify:** Cursor hot-reloads — no restart. Open **Cursor Settings → Tools & MCP** and look for a green dot next to `pdf-triage`.
[Docs](https://docs.cursor.com/context/model-context-protocol)
4. Windsurf
**Config file:** `~/.codeium/windsurf/mcp_config.json` (macOS/Linux), `%USERPROFILE%\.codeium\windsurf\mcp_config.json` (Windows).
Not created on first launch — create it yourself if missing.
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
**Verify:** Windsurf watches the file and hot-reloads on save. Tools appear in Cascade on the next chat session.
[Docs](https://docs.windsurf.com/windsurf/mcp)
5. VS Code + GitHub Copilot
> **The key is `servers`, not `mcpServers`.** This is the most common mistake when copying a config from Claude Desktop.
**Config file:** `.vscode/mcp.json` (workspace), or Command Palette → **MCP: Open User Configuration** (global).
```json
{
"servers": {
"pdf-triage": {
"type": "stdio",
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
**CLI alternative:**
```bash
code --add-mcp '{"name":"pdf-triage","command":"npx","args":["-y","pdf-triage-mcp","--root","/Users/me/Documents"]}'
```
**Verify:** MCP tools only work in **Agent mode** — switch from Ask/Edit to Agent in Copilot Chat, then click **Configure Tools** and confirm the `pdf_*` tools appear. Restart VS Code after first adding the file.
[Docs](https://code.visualstudio.com/docs/agent-customization/mcp-servers)
6. Zed
> **The key is `context_servers`, not `mcpServers`,** and `command` is a nested object rather than a string.
**Config file:** `~/.config/zed/settings.json` (macOS/Linux), `%APPDATA%\Zed\settings.json` (Windows). Command Palette → **zed: open settings**.
```json
{
"context_servers": {
"pdf-triage": {
"source": "custom",
"command": {
"path": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
],
"env": {}
}
}
}
}
```
If your Zed version rejects that, it predates the nested form — try `command`, `args` and `env` flat at the top level of the server object instead.
**Verify:** Agent Panel (`Cmd+Shift+A`) → gear icon → **MCP Servers**. Green dot means connected.
[Docs](https://zed.dev/docs/ai/mcp)
7. Cline (VS Code extension)
**Config file** — separate from VS Code's own:
| OS | Path |
|---|---|
| macOS | `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` |
| Windows | `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` |
| Linux | `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` |
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
],
"disabled": false,
"autoApprove": ["pdf_classify", "pdf_search"]
}
}
}
```
`autoApprove` runs the listed read-only tools without a confirmation prompt.
**Easier route:** Cline panel → MCP servers icon → **Edit MCP Settings** opens this file directly.
**Verify:** Panel refreshes automatically; green dot next to the server.
[Docs](https://docs.cline.bot/mcp/mcp-overview)
8. Continue.dev
**Config file:** `~/.continue/config.yaml` (global) or `.continue/config.yaml` (project). YAML is current; `config.json` is deprecated.
> `mcpServers` here is a **list**, not an object — and YAML needs spaces, never tabs.
```yaml
mcpServers:
- name: pdf-triage
command: npx
args:
- -y
- pdf-triage-mcp
- --root
- /Users/me/Documents
```
**Verify:** Reloads automatically on save. Switch Continue to **Agent mode** — MCP tools are unavailable in other modes.
[Docs](https://docs.continue.dev/customize/deep-dives/mcp)
9. Google Gemini CLI
**CLI:**
```bash
gemini mcp add pdf-triage npx -y pdf-triage-mcp --root /Users/me/Documents
# global instead of project-scoped
gemini mcp add --scope user pdf-triage npx -y pdf-triage-mcp --root /Users/me/Documents
```
**Or edit `~/.gemini/settings.json` (global) / `.gemini/settings.json` (project):**
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
],
"timeout": 30000,
"trust": false
}
}
}
```
**Verify:** Run `/mcp` inside a `gemini` session — servers show `CONNECTED` with their tool list. Or `gemini mcp list` from the shell.
[Docs](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html)
10. OpenAI Codex CLI
**Config file:** `~/.codex/config.toml` (global) or `.codex/config.toml` (project).
> TOML, and the key is `mcp_servers` — snake_case, never `mcpServers`.
```toml
[mcp_servers.pdf-triage]
command = "npx"
args = [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
startup_timeout_sec = 20
tool_timeout_sec = 60
```
**Verify:** `codex doctor --json` validates the config syntax. Note that it validates syntax only — it does not confirm the server actually spawned.
> **Known upstream issue:** several Codex CLI versions have a bug where stdio servers validate cleanly but silently fail to start, showing `Tools: none` in the TUI ([#3441](https://github.com/openai/codex/issues/3441), [#26810](https://github.com/openai/codex/issues/26810)). That is a Codex runtime bug, not a config error.
[Docs](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)
11. JetBrains AI Assistant / Junie
**AI Assistant** — configured through the IDE, no file to edit:
1. **Settings → Tools → AI Assistant → Model Context Protocol (MCP)**
2. **Add** → transport **STDIO**
3. Paste:
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
4. **OK → Apply**
**Junie** uses a file instead — `~/.junie/mcp/mcp.json` (global) or `.junie/mcp/mcp.json` (project), same JSON shape.
**Verify:** Check the **Status** column in the MCP settings panel; click it to list the server's tools.
[Docs](https://www.jetbrains.com/help/ai-assistant/mcp.html)
12. LM Studio
**Config file:** `~/.lmstudio/mcp.json` (macOS/Linux), `%USERPROFILE%\.lmstudio\mcp.json` (Windows).
Easier via the app: right sidebar → **Program** tab → **Install → Edit mcp.json**.
```json
{
"mcpServers": {
"pdf-triage": {
"command": "npx",
"args": [
"-y", "pdf-triage-mcp",
"--root", "/Users/me/Documents"
]
}
}
}
```
**Verify:** Auto-reloads on save; tools appear in the Program panel. LM Studio shows a confirmation dialog the first time a model calls a tool.
[Docs](https://lmstudio.ai/docs/app/mcp)