pdf-extract-mcp
pdf-extract-mcp
Сервер Model Context Protocol (MCP), который извлекает структурированные данные из неструктурированных PDF-документов детерминированно — извлечение обычного текста плюс сопоставление полей по регулярным выражениям/эвристикам, без вызовов LLM API во время извлечения.
Возможности
Настоящий MCP-сервер — построен на официальном MCP Python SDK (2.x), работает по протоколу через stdio, SSE или streamable HTTP. Проверен сквозным тестом, который запускает реальный сервер с официальным клиентом.
Извлечение на основе схем — укажите extract_fields на любую JSON Schema и получите структурированный JSON ровно для тех полей, которые вы запросили.
Детерминированность и прозрачность — сопоставление по регулярным выражениям/эвристикам, без вызовов LLM API, без скрытых затрат и чёрного ящика. Каждое извлечение воспроизводимо и проверяемо.
Понятные отчёты валидации — validate_against_schema объясняет по каждому полю, почему оно прошло проверку, не прошло или отсутствует.
Готовые схемы — invoice, resume и purchase_order поставляются готовыми к использованию, плюс синтетические образцы PDF, так что всё можно продемонстрировать сразу из коробки.
Корректная обработка ошибок — повреждённые PDF, отсутствующие файлы и неверные схемы возвращают структурированные ошибки, а не стек-трейсы.
Related MCP server: PDF RAG MCP Server
Что такое MCP и зачем это нужно
Model Context Protocol — это открытый стандарт, который позволяет ИИ-ассистентам (Claude, Cursor и т.д.) вызывать внешние инструменты через постоянное двунаправленное соединение. Вместо того чтобы вставлять текст PDF в чат и просить модель «разобраться», ассистент может напрямую вызвать pdf-extract-mcp, получить структурированный 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, текущая линия релизов, которая предоставляет API MCPServer). 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Это запускает MCP через stdio (режим по умолчанию, который ожидают 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, который находится в ~/Library/Application Support/Claude/ на macOS):
{
"mcpServers": {
"pdf-extract": {
"command": "python",
"args": ["/absolute/path/to/pdf-extract-mcp/server.py"]
}
}
}Перезапустите клиент после сохранения. Вы должны увидеть три новых инструмента: extract_fields, validate_against_schema и list_supported_document_types.
Инструменты
Tool | Назначение |
extract_fields(pdf_path, schema) | Извлекает структурированные поля из PDF по заданной JSON Schema -> {"ok": true, "data": {...}} |
validate_against_schema(data, schema) | Проверяет извлечённые данные по схеме -> отчёт passed/failed/missing с понятными пояснениями |
list_supported_document_types() | Выводит типы документов, для которых поставляются готовые схемы |
Аргумент schema инструмента extract_fields принимает объект JSON Schema, имя встроенной схемы (например, "invoice") или путь к .json-файлу схемы. Встроенные схемы находятся в 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Теперь вызовите extract_fields для образца счёта, используя имя встроенной схемы invoice. В Claude Code можно просто сказать: «извлеки поля из sample_pdfs/invoice.pdf с помощью схемы invoice»; под капотом при этом выполняется вызов инструмента, эквивалентный:
{
"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
}Передача данных в 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))Как работает регистрация инструментов MCP в server.py
Это сердце проекта, поэтому стоит точно понять, что 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: умеет отвечать на JSON-RPC-сообщения, которые клиент отправляет во время рукопожатия MCP (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="...").)
Вывод схемы — SDK анализирует аннотации типов функции (pdf_path: str, schema: dict) и автоматически генерирует JSON-схему входных данных инструмента. Именно поэтому MCP-клиент ещё до вызова знает, что pdf_path — строка, а schema — объект. Это тот же подход, что использует FastAPI: типы и есть контракт.
Описание — docstring становится описанием инструмента; Claude читает его, чтобы решить, когда вызывать инструмент и с какими аргументами.
Итак, когда клиент спрашивает сервер «что ты умеешь?» (tools/list), SDK отвечает именем, описанием и выведенной входной схемой для каждой декорированной функции — никакой ручной таблицы регистрации, которую нужно синхронизировать.
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") запускает цикл протокола: читает запросы JSON-RPC, разделённые переводами строк, из stdin, направляет их зарегистрированным инструментам и записывает ответы в stdout. Это и есть весь сервер — ни HTTP-фреймворка, ни маршрутов, ни ручной обработки запросов. (Для streamable-http / sse тот же вызов run() запускает внутреннее ASGI-приложение.)
Ещё одна деталь, на которую стоит обратить внимание: extract_fields использует небольшой вспомогательный хелпер _load_schema, который принимает словарь схемы, имя встроенной схемы или путь к файлу, — так что один и тот же инструмент работает как с "invoice", так и с полным объектом схемы. Сама функция извлечения остаётся строгой (только dict), а удобные преобразования выполняет серверный слой.
Как работает извлечение (детерминированно и прозрачно)
Извлечение текста — pdfplumber открывает PDF и извлекает обычный текст с каждой страницы.
Сопоставление полей — для каждого свойства в вашей схеме перебирается упорядоченный список регулярных выражений; побеждает первое совпадение (tools/extract.py -> _FIELD_PATTERNS). Сначала идут наиболее специфичные шаблоны, а для неизвестных имён полей используется универсальное сопоставление «Field Name: value» плюс таблица синонимов (_FIELD_ALIASES).
Приведение типов — сопоставленные строки приводятся к типу JSON Schema (например, "$1,750.00" -> 1750.0 для "type": "number"; разделение по запятой для массивов). При неудачном приведении используется исходная строка, чтобы не терять данные.
Валидация — 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."}
Неверная схема (пустая, без свойств или некорректная JSON Schema) -> структурированный ключ ошибки
Отсутствующие обязательные поля -> перечисляются в "missing"; некорректные значения -> перечисляются в "failed" с пояснениями
Тесты
pytest tests/ -v19 тестов, покрывающих:
Успешное извлечение для всех трёх типов документов (invoice, resume, purchase_order)
PDF с отсутствующими обязательными полями (негативное извлечение)
Проверка схемы, выявляющая неверный тип поля, отсутствующие обязательные поля, нарушения enum/pattern
Сценарии ошибок: повреждённый PDF, несуществующий файл, PDF без текста, неверная схема
Настоящий сквозной 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 рассчитан на API mcp 2.x (MCPServer). Если в вашем окружении mcp 1.x, переустановите с помощью pip install -U "mcp>=2.0".
Инструменты не появляются в Claude — перезапустите клиент после изменения конфигурации и убедитесь, что "args" указывает на абсолютный путь к server.py, при необходимости используя python из виртуального окружения в качестве команды.
Извлечение пропускает поле — добавьте для него шаблон в _FIELD_PATTERNS в tools/extract.py (или используйте универсальное запасное правило «Field Name: value» и таблицу синонимов).
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 gradedqualityAmaintenanceExtracts text and tables from PDFs for AI agents via MCP, enabling structured data retrieval from invoices, reports, and statements.75 PyPI1MIT
- FlicenseNot gradedqualityBmaintenanceEnables reading entire PDF documents into validated structured JSON, including tables, key-values, and markdown, with read-only extraction tools and layout reconstruction.-