About
Extract structured fields from documents once, query forever
README
tests lint Ruff License: MIT Python
dbt for unstructured data. Extract once, query forever.
Most tools re-read your documents every time you ask a question. docstore extracts the fields you care about once, caches them locally, and answers subsequent queries from the cache - no LLM calls, no re-reading, no waiting.
Run 1 (cold): 200 invoices → 400 LLM calls → ~$0.40
Run 2 (warm): 200 invoices → 0 LLM calls → $0.00
Query: "which invoices are unpaid?" → 0 LLM calls → <1s
Why this exists
LLMs made it easy to extract structured data from documents. What they did not provide is a layer that persists that extraction and invalidates it automatically when the source file changes. Every existing tool either:
- Re-reads raw documents on every query (expensive, slow)
- Requires a database or vector store (complex, overkill for most teams)
- Stores embeddings for semantic search (wrong abstraction for structured extraction)
docstore treats structured extraction as a cache over your unstructured data. Same insight as dbt applied to SQL - you define the transformation once, the system manages the state.
Benchmark
docstore ships with a reproducible public cache benchmark. It generates a
synthetic invoice corpus, writes ground_truth.jsonl, then measures:
cold_extract: empty cache, every document calls the LLM oncewarm_extract: same corpus and schema, every document is served from cachecached_query: filtered query against cached JSON — zero LLM calls, near-instant
# Synthetic invoices (auto-generated)
uv run python scripts/benchmark.py /tmp/docstore-benchmark --count 30
uv run python scripts/benchmark.py /tmp/docstore-benchmark --count 30 --filter "paid=false"
# Any existing folder of documents
uv run python scripts/benchmark.py ./my-documents \
--no-generate --schema my_schema --ask --glob "*.pdf" --filter "status=active"
Use --provider and --model to run it against a specific vendor. The
benchmark is intended to show cache behavior, not provider quality.
A contract-specific corpus generator is also included for demo purposes:
# Generate 30 synthetic vendor contract PDFs
uv run --extra scripts python scripts/generate_pdf_contracts.py ./sample_contracts
# Then benchmark against them
uv run python scripts/benchmark.py ./sample_contracts \
--no-generate --schema contracts --ask --glob "contract_*.pdf" --filter "status=active"
Installation
pip install lumient-docstore
All four LLM providers (Anthropic, OpenAI, Groq, Gemini) work out of the box - pick one at runtime via --provider.
Or from source:
git clone https://github.com/LumientAI/docstore
cd docstore
pip install -e ".[dev]"
Quickstart
Python API
from pathlib import Path
from docstore import DocStore, ExtractionSchema, create_llm_client
from docstore.agents.orchestrator import run_directory
class InvoiceSchema(ExtractionSchema):
vendor: str
amount: float
currency: str
due_date: str
paid: bool
invoices_dir = Path("./invoices")
# Co-locate the cache with the corpus so the CLI and Python API
# share state (the CLI's path-taking commands default to this).
store = DocStore(root=invoices_dir / ".docstore")
descriptor = InvoiceSchema.to_descriptor()
client = create_llm_client() # defaults to Anthropic; pass provider="openai" etc. to override
results = run_directory(invoices_dir, descriptor, store, client)
# Query without any LLM calls
unpaid = store.query("InvoiceSchema", lambda r: r.data.get("paid") is False)
CLI
# Generate a synthetic invoice corpus for testing (30 .txt files)
python scripts/generate_txt_invoices.py ./sample_invoices --count 30
# Extract - describe fields interactively
docstore shell ./invoices/
# Extract with a named schema
docstore extract ./invoices/ --schema invoice_schema
# Use OpenAI, Groq, or Gemini instead of the default Anthropic provider
docstore extract ./invoices/ --schema invoice_schema --provider openai
docstore extract ./invoices/ --schema invoice_schema --provider groq
docstore extract ./invoices/ --schema invoice_schema --provider gemini
# Override the default model for any provider
docstore extract ./invoices/ --schema invoice_schema --provider gemini --model gemini-2.5-pro
# Query stored results (no LLM)
docstore query invoice_schema --filter "is_paid=false" --store ./invoices/.docstore
# Aggregate: count and sum per vendor (no LLM)
docstore query invoice_schema --group-by vendor --count --sum amount --store ./invoices/.docstore
# Ask in natural language - one LLM call compiles to a filter,
# results come from cache with no per-document re-reads
docstore ask "which unpaid invoices are over $5000?" --schema invoice_schema --store ./invoices/.docstore
# Diff a changed file
docstore diff ./invoices/acme_april.pdf --schema invoice_schema
# Remove cache entries whose source file no longer exists
docstore sync --store ./invoices/.docstore # dry run
docstore sync --store ./invoices/.docstore --yes # delete stale entries
# Wipe the cache (optional --schema X to scope)
docstore clean --store ./invoices/.docstore --yes
# Stats
docstore stats --store ./invoices/.docstore
MCP server (Claude Desktop)
Add to your claude_desktop_config.json:
{
"mcpServers": {
"docstore": {
"command": "docstore-server",
"env": {
"DOCSTORE_DIR": "/path/to/your/.docstore",
"DOCSTORE_PROVIDER": "anthropic",
"ANTHROPIC_API_KEY": "your-key"
}
}
}
}
Claude can then call extract, query, diff, and stats directly.
Supported providers are anthropic (default), openai, groq, and gemini.
Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, or GEMINI_API_KEY
for the provider you choose. Each provider has a default model, and you can
override it with --model on the CLI or DOCSTORE_MODEL for the MCP server.
How it works
┌──────────────────────────────────────────────────────────────────┐
│ docstore pipeline │
│ │
│ document.pdf │
│ │ │
│ ▼ │
│ ┌─────────┐ cache hit? ──────────────────────────────┐ │
│ │ Parser │ ─────────────► .docstore/{key}.json │ │
│ └─────────┘ (no LLM) ──────────────────────────────┘ │
│ │ │
│ │ cache miss │
│ ▼ │
│ ┌───────────┐ │
│ │ Extractor │ 1 LLM call - extract fields against schema │
│ └───────────┘ │
│ │ │
│ │ (opt-in via --validate) │
│ ▼ │
│ ┌╌╌╌╌╌╌╌╌╌╌╌┐ │
│ ╎ Validator ╎ +1 LLM call - sanity-check extracted values │
│ └╌╌╌╌╌╌╌╌╌╌╌┘ │
│ │ │
│ ▼ │
│ .docstore/{file_hash}__{schema}__{version}.json │
└──────────────────────────────────────────────────────────────────┘
The validator is off by default - cold extraction is one LLM call per file. Pass --validate to add a plausibility check (doubles cost; see the CLI reference for trade-offs).
Cache key: sha256(file_bytes)[:16] + schema_name + sha256(json.dumps(fields, sort_keys=True))[:12]
The cache invalidates automatically when:
- The file content changes (file hash changes)
- The schema changes (schema version changes)
- A different schema is applied to the same file (different key)
Schema definition
Two ways to define a schema:
1. Python class (recommended for code)
from docstore import ExtractionSchema
class ContractSchema(ExtractionSchema):
parties: list
start_date: str
end_date: str
obligations: list
auto_renews: bool
2. Natural language via CLI (recommended for ad-hoc use)
docstore shell ./contracts/
# > vendor name, contract start date, expiry date, whether it auto-renews
The orchestrator normalises your description into a canonical schema and shows it to you before running.
Supported file types
PDF, DOCX, TXT, MD, CSV, HTML, JSON
PDF support covers documents with embedded/selectable text. Scanned or image-only PDFs need OCR, which docstore does not support yet.
Limitations vs Lumient
docstore is a single-document extraction cache. It does not:
- Compose records across documents (invoice + Stripe → reconciliation status)
- Trigger automatically when files arrive
- Maintain a queryable entity layer with lineage
- Support multi-step workflow logic (validate, diff, generate)
- Provide governance and audit trails for regulated industries
For cross-document composition and maintained operational records, see Lumient.
Development
# uv (recommended)
uv sync --all-extras
uv run pytest
uv run ruff check .
# Or with pip
pip install -e ".[dev]"
pytest tests/
See AGENTS.md for architectural invariants and CONTRIBUTING.md for the PR process.
License
MIT
Installing Docstore
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/LumientAI/docstoreFAQ
Is Docstore MCP free?
Yes, Docstore MCP is free — one-click install via Unyly at no cost.
Does Docstore need an API key?
No, Docstore runs without API keys or environment variables.
Is Docstore hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Docstore in Claude Desktop, Claude Code or Cursor?
Open Docstore on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Docstore with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
