Some checks are pending
Security Scan / security-scan (push) Waiting to run
Dynamic XFA forms — real-estate forms, mortgage forms, government forms —
have been a silent failure mode for every tool in this server: the layout
and fields live in an XFA program that only Adobe's runtime executes, so
PyMuPDF/pdfium/MuPDF only see the "Open in Adobe Reader" placeholder page.
extract_form_data returned "document closed", convert_to_images returned
the placeholder, analyze_pdf_health reported total_pages=1 — all
correctly per the visible PDF, but all misleading about what the form
actually contains.
New capabilities:
- src/mcp_pdf/xfa.py — XFA detection, packet extraction, field parsing,
classification. No new deps (pypdf + stdlib ElementTree). Lifted from
a working prototype with attribution preserved; parameterized for
producer profiles.
- is_xfa_pdf — MCP tool to detect XFA presence + classify dynamic vs
static. Use for branching BEFORE extract_form_data or convert_to_images.
- extract_xfa_fields — MCP tool that parses the XFA template for field
names, captions, UI types. Splits fields into shared (cross-form
canonical Global_Info-* vocabulary), positional (opaque codes like
p01tf022), and plumbing (producer internals, dropped). Defaults to
the zipForm producer profile; pass profile="generic" + custom regex
patterns for other producers. canonical_separator selects _ / . / -.
UX fixes for existing tools (no more cryptic failures on dynamic XFA):
- extract_form_data — diagnoses dynamic XFA and returns
{error, hint: "extract_xfa_fields"} instead of "document closed"
- convert_to_images — still produces the rendered image (caller may
want the placeholder), but now warns that it's not the real form
- analyze_pdf_health — surfaces is_xfa + xfa_type in document_stats,
adds a warning for dynamic XFA
Cross-tool alignment:
- extract_form_data field-type strings aligned to the same six-term
vocabulary as XFA (text/checkbox/radio/dropdown/date/signature +
button/unknown edge categories). listbox + combobox both collapse
to "dropdown" — the widget-hover distinction wasn't semantic.
Tests: 31/31 passing against the synthetic XFA fixture
(tests/fixtures/xfa/synthetic_dynamic_xfa.pdf). The fixture is
hand-built, license-clean, ~2 KB, and exercises all three classification
categories including the "denylist beats shared-prefix" invariant
(Global_Info-Invisibind-Test drops despite the shared prefix).
Build hygiene:
- .gitignore exception !tests/fixtures/**/*.pdf so fixtures survive
the global *.pdf rule
- pyproject.toml force-include for the fixture (uses hatchling's
force-include not include — the latter is restrictive, not additive)
Coordinated via the agent-thread protocol; full design history is
in docs/agent-threads/xfa-form-support/ (excluded from the sdist).
161 lines
4.4 KiB
TOML
161 lines
4.4 KiB
TOML
[project]
|
|
name = "mcp-pdf"
|
|
version = "2.3.0"
|
|
description = "Secure FastMCP server for comprehensive PDF processing - text extraction, OCR, table extraction, forms, annotations, and more"
|
|
authors = [{name = "Ryan Malloy", email = "ryan@malloys.us"}]
|
|
readme = "README.md"
|
|
license = {text = "MIT"}
|
|
requires-python = ">=3.10"
|
|
keywords = [
|
|
"mcp",
|
|
"fastmcp",
|
|
"pdf",
|
|
"ocr",
|
|
"text-extraction",
|
|
"table-extraction",
|
|
"pdf-processing",
|
|
"api",
|
|
"integration"
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Intended Audience :: Developers",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
"Topic :: Text Processing :: General",
|
|
"Topic :: Office/Business",
|
|
]
|
|
dependencies = [
|
|
"fastmcp>=0.1.0",
|
|
"httpx>=0.25.0",
|
|
"pydantic>=2.0.0",
|
|
"python-dotenv>=1.0.0",
|
|
"PyMuPDF>=1.23.0",
|
|
"pdfplumber>=0.10.0",
|
|
"pytesseract>=0.3.10",
|
|
"pdf2image>=1.16.0",
|
|
"pypdf>=6.0.0",
|
|
"pandas>=2.0.0",
|
|
"Pillow>=10.0.0",
|
|
"markdown>=3.5.0",
|
|
]
|
|
|
|
[project.urls]
|
|
Homepage = "https://github.com/rsp2k/mcp-pdf"
|
|
Documentation = "https://github.com/rsp2k/mcp-pdf#readme"
|
|
Repository = "https://github.com/rsp2k/mcp-pdf.git"
|
|
Issues = "https://github.com/rsp2k/mcp-pdf/issues"
|
|
Changelog = "https://github.com/rsp2k/mcp-pdf/releases"
|
|
|
|
[project.scripts]
|
|
mcp-pdf = "mcp_pdf.server:main"
|
|
mcp-pdf-legacy = "mcp_pdf.server_legacy:main"
|
|
mcp-pdf-modular = "mcp_pdf.server_refactored:main"
|
|
|
|
[project.optional-dependencies]
|
|
# Form creation features (create_form_pdf, advanced form tools)
|
|
forms = [
|
|
"reportlab>=4.0.0",
|
|
]
|
|
|
|
# Advanced table extraction (camelot needs Ghostscript, tabula needs Java)
|
|
tables = [
|
|
"camelot-py[cv]>=0.11.0",
|
|
"tabula-py>=2.8.0",
|
|
]
|
|
|
|
# Markdown → PDF conversion (requires pandoc binary + a PDF engine such as
|
|
# xelatex, pdflatex, tectonic, weasyprint, or wkhtmltopdf)
|
|
markdown = [
|
|
"pypandoc>=1.13",
|
|
]
|
|
|
|
# All optional features
|
|
all = [
|
|
"reportlab>=4.0.0",
|
|
"camelot-py[cv]>=0.11.0",
|
|
"tabula-py>=2.8.0",
|
|
"pypandoc>=1.13",
|
|
]
|
|
|
|
# Development dependencies
|
|
dev = [
|
|
"pytest>=7.0.0",
|
|
"pytest-asyncio>=0.21.0",
|
|
"black>=23.0.0",
|
|
"ruff>=0.1.0",
|
|
"mypy>=1.0.0",
|
|
"build>=0.10.0",
|
|
"twine>=4.0.0",
|
|
"safety>=3.0.0",
|
|
"pip-audit>=2.0.0",
|
|
]
|
|
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
# Keep dev-only artifacts out of the sdist that ships to PyPI.
|
|
# (The PII audit also runs against the unpacked sdist before each publish —
|
|
# see ~/.claude/rules/python.md.)
|
|
[tool.hatch.build.targets.sdist]
|
|
# force-include re-adds files that gitignore (e.g. `*.pdf`) would otherwise
|
|
# block — the fixture under tests/fixtures/ is small, deliberate, and required
|
|
# by tests/test_xfa.py at runtime. Unlike `include`, force-include is additive,
|
|
# not restrictive.
|
|
force-include = {"tests/fixtures/xfa/synthetic_dynamic_xfa.pdf" = "tests/fixtures/xfa/synthetic_dynamic_xfa.pdf"}
|
|
exclude = [
|
|
"CLAUDE.md", # operator-private project context
|
|
".env", ".env.local", # never ship credentials
|
|
".mcp.json", # contains local filesystem paths
|
|
".pytest_cache/",
|
|
".ruff_cache/",
|
|
"build/",
|
|
"dist/",
|
|
"examples/page_001.png",
|
|
"examples/*.pdf", # demo PDFs are large + not needed by end users
|
|
"examples/test_demo.*",
|
|
"tests/CopperSprings_DigitalPortfolio.pdf", # large fixture PDF
|
|
"**/page_*.png", # convert_to_images output artifacts (test side-effects)
|
|
"docs/agent-threads/", # project coordination history, not for end users
|
|
"test_security_features.py",
|
|
"test_integration.py",
|
|
"MCPMIXIN_*.md", # internal architecture/migration notes
|
|
"MCP_DOCX_TOOLS_PLAN.md",
|
|
"claude-mcp-manager", # personal helper script
|
|
"run-mcp-server.sh",
|
|
"docker-compose.yml",
|
|
"Dockerfile",
|
|
]
|
|
|
|
[tool.pytest.ini_options]
|
|
asyncio_mode = "auto"
|
|
addopts = "-v --tb=short"
|
|
testpaths = ["tests"]
|
|
python_files = ["test_*.py", "*_test.py"]
|
|
|
|
[tool.hatchling.build.targets.sdist]
|
|
include = [
|
|
"/src",
|
|
"/tests",
|
|
"/examples",
|
|
"README.md",
|
|
"LICENSE",
|
|
"MANIFEST.in",
|
|
]
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
"pip-audit>=2.9.0",
|
|
"pytest>=8.4.1",
|
|
"pytest-asyncio>=1.1.0",
|
|
"pytest-cov>=6.2.1",
|
|
"reportlab>=4.4.3",
|
|
"safety>=3.2.11",
|
|
"twine>=6.1.0",
|
|
]
|