pdf-extract-mcp
pdf-extract-mcp
一个模型上下文协议(Model Context Protocol,MCP)服务器,用于确定性地从非结构化 PDF 文档中提取结构化数据——采用纯文本提取加正则表达式/启发式字段匹配,在提取时不调用任何 LLM API。
功能特性
真正的 MCP 服务器——基于官方 MCP Python SDK(2.x)构建,通过 stdio、SSE 或 streamable HTTP 使用该协议通信。由一项端到端测试验证,该测试使用官方客户端驱动实际服务器。
Schema 驱动的提取——将 extract_fields 指向任意 JSON Schema,即可返回与你所请求字段完全对应的结构化 JSON。
确定且可检查——正则/启发式匹配,无 LLM API 调用,无隐藏成本,无黑盒。每次提取都可重复并可审计。
人类可读的验证报告——validate_against_schema 会逐字段解释其通过、失败或缺失的原因。
预置 schema——invoice、resume 和 purchase_order 开箱即用,另附合成示例 PDF,所有功能均可开箱演示。
优雅的错误处理——损坏的 PDF、缺失的文件和错误的 schema 会返回结构化错误,绝不抛出堆栈跟踪。
Related MCP server: PDF RAG MCP Server
什么是 MCP,为什么它很有用
模型上下文协议(Model Context Protocol)是一种开放标准,允许 AI 助手(Claude、Cursor 等)通过持久化的双向连接调用外部工具。助手无需把 PDF 文本粘贴到聊天中再让模型“自行解读”,而是可以直接调用 pdf-extract-mcp,接收与你提供的 schema 匹配的结构化 JSON,并据此采取行动。由于这里的提取是确定性的(正则表达式 + 启发式),而非概率性的模型调用,因此每个结果都可检查、可重复且成本低廉。这使得它非常适合自动化文档流水线(发票到财务、简历到 ATS、采购订单到采购流程)——在这些场景中,你需要知道为什么某个字段会以某种方式被提取。
安装
cd pdf-extract-mcp
python3 -m venv .venv
source .venv/bin/activate
make install # pip install -e ".[dev]" (installs the console script too)或者,使用纯 pip:
pip install -e ".[dev]"该服务器使用官方 MCP Python SDK(mcp >= 2.x,当前发布线,提供 MCPServer API)。pdfplumber 负责文本提取,jsonschema 负责验证,reportlab 生成示例 PDF。
安装后还会提供一个 pdf-extract-mcp 控制台脚本,因此你可以从任何位置运行服务器:
pdf-extract-mcp # stdio (default)
pdf-extract-mcp --transport streamable-http --host 127.0.0.1 --port 8000运行
python server.py这会通过 stdio 提供 MCP 服务(这是默认方式,也是 Claude Code / Claude Desktop 所期望的方式)。你还可以将其暴露为网络服务:
python server.py --transport streamable-http --host 127.0.0.1 --port 8000
python server.py --transport sse --host 127.0.0.1 --port 8001连接 Claude Code / Claude Desktop
Claude Code — 在项目根目录添加 .mcp.json:
{
"mcpServers": {
"pdf-extract": {
"command": "python",
"args": ["/absolute/path/to/pdf-extract-mcp/server.py"],
"env": {}
}
}
}Claude Desktop — 将同样的配置块添加到你的 Claude Desktop 配置中(claude_desktop_config.json,在 macOS 上位于 ~/Library/Application Support/Claude/ 下):
{
"mcpServers": {
"pdf-extract": {
"command": "python",
"args": ["/absolute/path/to/pdf-extract-mcp/server.py"]
}
}
}保存后重启客户端。你应该会看到三个新工具:extract_fields、validate_against_schema 和 list_supported_document_types。
工具
工具 | 用途 |
extract_fields(pdf_path, schema) | 从 PDF 中提取与 JSON Schema 匹配的结构化字段 -> {"ok": true, "data": {...}} |
validate_against_schema(data, schema) | 根据 schema 检查提取的数据 -> 通过/失败/缺失报告,附人类可读的原因 |
list_supported_document_types() | 列出随预置 schema 一起提供的文档类型 |
extract_fields 的 schema 参数接受 JSON Schema 对象、内置 schema 的名称(例如 "invoice"),或 .json schema 文件的路径。内置 schema 位于 schemas/ 目录:
invoice — vendor_name、invoice_number、total_amount、due_date(必填)+ issue_date、customer_name
resume — name、email(必填)+ phone、skills
purchase_order — po_number、vendor_name、total_amount(必填)+ issue_date、customer_name
工作示例
首先生成示例 PDF(仓库中已存在;随时可用以下命令重新生成):
python sample_pdfs/generate_samples.py现在使用内置的 invoice schema 名称对示例发票调用 extract_fields。在 Claude Code 中,你只需说“使用 invoice schema 从 sample_pdfs/invoice.pdf 中提取字段”;其底层会发出一个等价于以下内容的工具调用:
{
"name": "extract_fields",
"arguments": {
"pdf_path": "/absolute/path/to/pdf-extract-mcp/sample_pdfs/invoice.pdf",
"schema": "invoice"
}
}实际预期结果:
{
"ok": true,
"data": {
"vendor_name": "Acme Widgets Corp",
"invoice_number": "INV-2024-0087",
"total_amount": 1750.0,
"due_date": "April 1, 2024",
"issue_date": "March 1, 2024",
"customer_name": "Globex Industries"
},
"text_length": 372
}使用同一个 schema 将数据提供给 validate_against_schema:
{
"ok": true,
"valid": true,
"passed": ["customer_name", "due_date", "invoice_number", "issue_date", "total_amount", "vendor_name"],
"failed": [],
"missing": [],
"summary": "Valid: all 6 present field(s) conform to the schema.",
"error": null
}直接从 Python 运行这些代码即可实时查看效果:
import json
from tools.extract import extract_fields
from tools.validate import validate_against_schema
schema = json.load(open("schemas/invoice.json"))
result = extract_fields("sample_pdfs/invoice.pdf", schema)
print(result["data"])
print(validate_against_schema(result["data"], schema))server.py 中 MCP 工具注册的工作原理
这是项目的核心,因此值得准确理解 SDK 在幕后为你做了些什么。
1. 创建服务器对象。
from mcp.server.mcpserver import MCPServer
mcp = MCPServer(
"pdf-extract-mcp",
title="PDF Extract MCP",
description="Deterministic structured-data extraction from PDF documents",
version="0.2.0",
)MCPServer 是 mcp SDK 2.x 的服务器类。它实现了 MCP 线路协议:知道如何应答客户端在 MCP 握手期间发送的 JSON-RPC 消息(initialize、tools/list、tools/call 等)。构造函数参数是元数据——服务器的名称(协议握手所必需)以及可选的 title/description/version,客户端可能会将其展示给用户。
2. 使用装饰器注册每个工具。
@mcp.tool()
def extract_fields(pdf_path: str, schema: dict) -> dict:
"""Extract structured fields from an unstructured PDF ..."""
return _extract_fields(pdf_path, schema)该装饰器为你完成三项工作:
名称注册 — 函数名 extract_fields 成为客户端调用该工具时使用的工具名称。(你可以通过 @mcp.tool(name="...") 覆盖它。)
Schema 推断 — SDK 会检查函数的类型注解(pdf_path: str、schema: dict),并自动生成工具的 JSON 输入 schema。这正是 MCP 客户端在调用之前就知道 pdf_path 是字符串、schema 是对象的原因。这与 FastAPI 使用的模式相同——类型就是契约。
描述 — 文档字符串(docstring)成为工具的描述,Claude 会阅读它来决定何时调用该工具以及使用哪些参数。
因此,当客户端询问服务器“你能做什么?”(tools/list)时,SDK 会为每个被装饰的函数返回名称、描述和推断出的输入 schema——无需手动维护注册表来保持同步。
3. 函数体就是普通的 Python 代码。
当客户端调用该工具(带参数的 tools/call)时,SDK 会反序列化 JSON 参数,用它们调用你的函数,并将返回值通过线路协议序列化回去。返回值就是客户端看到的内容——这就是为什么这些工具总是返回普通的可 JSON 序列化字典,并且绝不抛出异常:异常会变成不透明的协议错误,而结构化的 {"ok": false, "error": "..."} 字典则可以被 Claude 读取并作出响应。实际的提取/验证逻辑位于 tools/extract.py 和 tools/validate.py 中,因此即使没有 MCP 客户端,这些逻辑也保持可单元测试。
4. 运行它。
if __name__ == "__main__":
main() # argparse -> mcp.run(transport="stdio")mcp.run(transport="stdio") 启动协议循环:它从 stdin 读取换行分隔的 JSON-RPC 请求,将其分发给已注册的工具,并将响应写入 stdout。这就是整个服务器——没有 HTTP 框架、没有路由、没有手动请求处理。(对于 streamable-http / sse,相同的 run() 调用会启动一个内部 ASGI 应用。)
还有一个值得注意的细节:extract_fields 使用一个很小的辅助函数 _load_schema,它接受 schema 字典、内置 schema 名称或文件路径——因此同一个工具既可以处理 "invoice",也可以处理完整的 schema 对象。实际的提取函数保持严格(仅接受 dict),由服务器层处理便捷转换。
提取的工作原理(确定性、可检查)
文本提取 — pdfplumber 打开 PDF 并提取每一页的纯文本。
字段匹配 — 对于 schema 中的每个属性,按顺序尝试一系列正则表达式;第一个匹配者胜出(tools/extract.py -> _FIELD_PATTERNS)。模式按“最具体优先”排序,未知字段名会回退到通用的“字段名: 值”匹配,外加同义词表(_FIELD_ALIASES)。
类型强制转换 — 匹配到的字符串会被转换为 JSON Schema 类型(例如 "type": "number" 时 "$1,750.00" -> 1750.0;数组则按逗号拆分)。强制转换失败时会回退到原始字符串,而不是丢失数据。
验证 — validate_against_schema 使用 jsonschema 包重新检查提取的数据,并逐字段报告其是通过、失败(附人类可读的原因),还是完全缺失。
由于每一步都是普通代码,你可以准确追踪某个字段为什么被提取或未被提取——没有黑盒。
错误处理
这三个工具在任何路径下都返回结构化 JSON——它们绝不会跨 MCP 边界抛出堆栈跟踪:
损坏/无法读取的 PDF -> {"ok": false, "error": "Could not read PDF ..."}
文件缺失 -> {"ok": false, "error": "PDF not found: ..."}
无可提取文本的 PDF -> {"ok": false, "error": "... contains no extractable text."}
无效的 schema(为空、没有 properties,或不是有效的 JSON Schema)-> 返回结构化错误键
缺少必填字段 -> 在 "missing" 中列出;格式错误的值 -> 在 "failed" 中列出并附原因
测试
pytest tests/ -v19 个测试,覆盖:
对全部三种文档类型(invoice、resume、purchase_order)的成功提取
缺少必填字段的 PDF(反向提取)
schema 验证能够捕获字段类型错误、缺少必填字段、enum/pattern 违规
错误路径:损坏的 PDF、不存在的文件、无文本的 PDF、无效的 schema
一项真正的端到端 MCP 测试(tests/test_mcp_end_to_end.py),它将 server.py 作为子进程启动,通过 stdio 与官方 MCP 客户端连接,并通过协议调用全部三个工具——证明这是一个真正的 MCP 服务器,而非伪装成服务器的库
示例 PDF 如果缺失,会由 tests/conftest.py 自动重新生成。
仓库结构
pdf-extract-mcp/
server.py # MCP server: MCPServer + tool registration + transports
tools/
__init__.py
extract.py # pdfplumber text extraction + regex field matching
validate.py # jsonschema validation with structured reports
schemas/
invoice.json # pre-built schema: invoice
resume.json # pre-built schema: resume
purchase_order.json # pre-built schema: purchase_order
sample_pdfs/
generate_samples.py # reportlab generator for the 4 sample PDFs
invoice.pdf
invoice_missing_fields.pdf
resume.pdf
purchase_order.pdf
tests/
conftest.py # auto-generates sample PDFs if missing
test_tools.py # unit tests for extract/validate
test_mcp_end_to_end.py # end-to-end test over the real MCP stdio transport
README.md
requirements.txt故障排查
ModuleNotFoundError: No module named 'mcp' — 你不在虚拟环境中:请运行 source .venv/bin/activate(或使用 ./.venv/bin/python server.py)。
FastMCP 导入错误 — server.py 面向 mcp 2.x API(MCPServer)。如果你的环境中有 mcp 1.x,请使用 pip install -U "mcp>=2.0" 重新安装。
Claude 中不显示工具 — 编辑配置后重启客户端,并确保 "args" 指向 server.py 的绝对路径,必要时使用虚拟环境中的 python 作为命令。
提取时漏掉某个字段 — 在 tools/extract.py 的 _FIELD_PATTERNS 中为其添加模式(或依赖通用的“字段名: 值”回退以及同义词表)。
Available Tools
3 toolsextract_fieldsB
Extract structured fields from an unstructured PDF using regex/heuristics.
Args: pdf_path: Path to the PDF file. schema: JSON Schema describing the fields to extract (must have a non-empty 'properties' object). A built-in schema name (e.g. "invoice") or a path to a .json schema file is also accepted.
Returns: {"ok": True, "data": {...}, "text_length": N} on success, or {"ok": False, "error": "..."} on failure (corrupt PDF, bad schema, ...).
| Name | Required | Description | Default |
|---|---|---|---|
| schema | Yes | ||
| pdf_path | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full disclosure burden. It does disclose failure modes (corrupt PDF, bad schema), the exact return contract ({ok: True/False}), and the non-empty properties requirement. However, it omits the side-effect profile (e.g., read-only disk access) and edge behaviors such as empty extraction results, so disclosure is solid but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in a single line, followed by a clean Args/Returns structure that is scannable. Slightly padded by trailing ellipses and the '...' after the failure list, but overall efficient with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no annotations and no output schema, the description covers the return format, failure modes, and semantic details of both parameters—enough to call the tool correctly. Gaps are minor: no sibling-routing guidance and the meaning of the returned 'text_length' field is undefined.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must supply meaning. It adds substantial context: schema may be a JSON Schema object, a built-in name like 'invoice', or a .json file path, and must have a non-empty 'properties'; pdf_path is explained as a path to the PDF. This goes well beyond the bare type declarations.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb (extract), a resource (structured fields), and a source/method (unstructured PDF using regex/heuristics). This clearly distinguishes extraction behavior from list_supported_document_types, though it doesn't explicitly name the differentiation from validate_against_schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance on when to choose this tool versus its siblings. It doesn't mention when validate_against_schema or list_supported_document_types would be the better pick, and states no preconditions or exclusions beyond the inline schema-constraint note in Args. The agent must infer usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_supported_document_typesB
List document types with pre-built schemas (e.g. invoice, resume, purchase_order).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It weakly implies a read-only operation (listing) but does not explicitly state side effects, authentication requirements, rate limits, or response behavior. The existence of an output schema covers return format, but not behavioral traits. A simple 'List' implies read-only, but for a complete definition more context is expected.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence that leads with the action and then clarifies with concrete examples. There is no filler, and every word contributes to understanding the tool's purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has no parameters, an output schema exists, and the purpose is simple, the description covers the essentials. It explains what the tool does and gives representative examples. It could add a note about usage direction (e.g., that these types can be used with extract_fields or validate_against_schema), but such cross-referencing is not required for basic invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the input schema is an empty object. According to the rubric, a parameterless tool gets a baseline 4. The description adds no parameter-specific information, but none is needed; the schema is sufficient.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb 'List' and a clear resource 'document types with pre-built schemas', including helpful examples (invoice, resume, purchase_order). It clearly conveys the tool's function, though it does not explicitly distinguish it from siblings like extract_fields or validate_against_schema, so it falls just short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus the sibling tools. The description does not mention alternatives, typical usage scenarios, or any conditions that would make this tool the right choice. This is a clear gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_against_schemaA
Validate extracted data against a JSON Schema.
Args: data: The data to check (e.g. the "data" from extract_fields). schema: JSON Schema to validate against.
Returns: A structured report with passed/failed/missing field lists and a human-readable reason for each failure. Never raises: invalid schemas are reported via the "error" key instead.
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | ||
| schema | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses key behavior: never raises exceptions, invalid schemas are reported via an 'error' key, and the return is a structured report with passed/failed/missing lists and human-readable reasons. This goes beyond a generic 'validates' and gives the agent a clear expectation of failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is efficiently formatted with an Args and Returns section, front-loaded with the purpose sentence. It is not verbose—every line contributes to the agent's understanding. It could be tightened (the Returns section is slightly redundant with the behavioral note), but it's well-structured and clear.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given two required parameters, no output schema, and no annotations, the description covers the essential context: what the tool does, what each parameter is, how errors are handled, and what the response contains. It references the sibling workflow (extract_fields) which aids routing. The only minor gap is that it doesn't specify the expected shape of 'data' beyond being an object, but the example mitigates this.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must explain the parameters. It does: 'data' is explicitly the output of extract_fields, and 'schema' is a JSON Schema. This adds meaning beyond the bare 'object' type in the schema and gives the agent the necessary context for both arguments. It could add more detail about expected structures, but it is sufficient for a two-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear verb+resource: 'Validate extracted data against a JSON Schema.' It names the specific tool and its input, and the example referencing extract_fields ties it to the workflow. It is clearly distinct from the siblings (list_supported_document_types and extract_fields) because validation is an entirely separate operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context for when to use it: after extraction, with the 'data' from extract_fields. It implies the pipeline (extract → validate) without explicitly saying 'use this when...' but the example makes the usage obvious. It does not mention when not to use it or name alternatives, so it lacks an explicit exclusion but is not misleading.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.2.0- First observed
extract_fields - First observed
list_supported_document_types - First observed
validate_against_schema
TDQS
Scored across 3 tools
Each tool serves a clearly distinct purpose: listing supported schemas, extracting fields from a PDF, and validating extracted data against a schema. There is no overlap or ambiguity in tool responsibilities.
All tool names follow a consistent verb_noun pattern in snake_case: list_supported_document_types, extract_fields, validate_against_schema. The convention is uniform and predictable.
With only 3 tools, the set is at the lower end of the well-scoped range but still appropriate for a focused PDF extraction and validation server. Each tool earns its place without redundancy.
The tool surface covers the core workflow: discovering available schemas, extracting fields, and validating results. Minor gaps exist (e.g., no tool to add custom schemas or handle batch processing), but they are not critical for the primary purpose.
Maintenance
Related MCP Connectors
High-fidelity PDF to structured Markdown conversion and document field extraction.
Deterministic PDF tools for AI agents: inspect fields, fill forms, merge PDFs, invoices.
Turn PDFs and photos into typed JSON with your own schema. Missing values come back null.
Turn any PDF into structured JSON via AI + OCR: invoices, bank statements, contracts.
Related MCP Servers
- AlicenseAqualityDmaintenanceExtracts structured JSON data from unstructured text using predefined schemas for receipts, invoices, resumes, and emails. It allows users to transform messy text into organized data through built-in or custom-defined fields.1MIT
- AlicenseAqualityDmaintenanceEnables RAG over messy PDFs — extract, chunk, embed, and search scanned, multi-column, and table-heavy documents.6MIT
- AlicenseNot gradedqualityCmaintenanceExtracts text and tables from PDFs for AI agents via MCP, enabling structured data retrieval from invoices, reports, and statements.75 PyPI1MIT
- FlicenseNot gradedqualityCmaintenanceEnables reading entire PDF documents into validated structured JSON, including tables, key-values, and markdown, with read-only extraction tools and layout reconstruction.-