Procurement Knowledge
FreeNot checkedMCP server for querying procurement and inventory documents (invoices, purchase orders, etc.) via structured lookups, precomputed comparisons, and full-text sea
About
MCP server for querying procurement and inventory documents (invoices, purchase orders, etc.) via structured lookups, precomputed comparisons, and full-text search.
README
Local data pipeline and MCP server for querying a procurement and inventory document corpus (invoices, purchase orders, shipping orders, inventory reports, contracts).
Status: Pipeline and MCP server complete. Run make ingest once, then connect an MCP client to query preprocessed artifacts.
Design decisions and trade-offs: CAPABILITY_TRACKER.md.
Requirements
- Python 3.11+
- uv package manager
- Tesseract OCR (
brew install tesseracton macOS) - Cursor or another MCP-compatible client
Setup
Extract corpus data
From the project root, unzip the bundled archive. It creates data/ in place (no copying or rearranging files):
unzip test-data.zip
Expected layout: data/invoices/, data/purchase_orders/, data/shipping_orders/, data/inventory_reports/, data/contracts/ (~45 documents).
Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
Virtual environment on external drives
If the project lives on an external volume, uv may fail to create .venv on that drive due to macOS ._* (AppleDouble) files.
cp .env.example .env
Edit .env:
UV_PROJECT_ENVIRONMENT=/Users/you/.venvs/procurement-knowledge-mcp
mkdir -p ~/.venvs
Use make or source .env before bare uv run commands.
Install dependencies
make sync
# or: uv sync --all-groups
Development workflow
make # same as make check
make check # ruff + mypy + full pytest with coverage (CI / pre-commit gate)
make test-fast # unit tests only, no coverage (skips corpus ingest)
make test # full pytest with terminal coverage
make test-integration # slow corpus / ingest tests only
make test-cov # pytest + htmlcov/
make cursor-setup # sync deps + verify MCP launcher
make smoke # MCP tool smoke test (requires make ingest)
make ingest # test-fast, then full ingest
make mcp # test-fast, then MCP server (stdio)
Coverage artifacts (.coverage, htmlcov/) are gitignored.
Architecture
flowchart LR
subgraph ingest [ingest.py]
D[discover] --> E[extract] --> M[model]
M --> C[compare] --> CH[chunk] --> I[index]
end
subgraph artifacts [processed/ and index/]
DJ[documents.jsonl]
OI[order_index.json]
CAT[catalog.json]
OC[order_comparisons.json]
CJ[chunks.jsonl]
DB[knowledge.db]
end
subgraph mcp [mcp_server.py]
T1[search_documents]
T2[list_documents]
T3[get_document]
T4[find_document_gaps]
T5[compare_order_documents]
T6[get_knowledge_base_summary]
end
data[(data/)] --> D
M --> DJ
M --> OI
C --> CAT
C --> OC
CH --> CJ
I --> DB
DJ --> mcp
CAT --> mcp
OC --> mcp
DB --> T1
Pipeline
discover -> extract -> model -> compare -> chunk -> index
| Stage | Module | Output |
|---|---|---|
| discover | discover.py |
File registry, stable doc_id, basename collision index |
| extract | extract.py |
PyMuPDF text; Tesseract OCR for JPGs / empty PDFs |
| model | parse.py, model.py |
documents.jsonl, order_index.json |
| compare | compare.py |
catalog.json, order_comparisons.json |
| chunk | chunk.py |
chunks.jsonl |
| index | index.py |
knowledge.db (SQLite FTS5) |
Typical ingest result: 45 documents, 19 orders, 130 chunks.
Data modeling
- Content-primary fields:
order_id, totals, line items, inventoryperiodparsed from document body (or OCR), withfield_provenanceon each field. - Path-based identity only:
doc_id,source_path,doc_type(from folder). Filename patterns are fallbacks, never silent authority for linking. - Order index: maps content-derived
order_idtodoc_idlists. Scanned JPG invoices without OCRorder_idare excluded and listed incatalog.json. - Comparisons: precomputed per-order presence, total matching (?0.01), and line-item checks in
order_comparisons.json.
Retrieval strategy
| Query type | Mechanism |
|---|---|
| Structured lookup (order, doc type, gaps) | documents.jsonl, order_index.json, catalog.json |
| Cross-document reconciliation | order_comparisons.json |
| Text evidence (contracts, keywords) | FTS5 over chunks.jsonl in knowledge.db |
Chunking: one chunk per page (invoice, PO, shipping, contract); one chunk per inventory report; contract boilerplate stripped.
Identity and duplicate handling
| Rule | Behavior |
|---|---|
source_path |
Normalized relative path; duplicate paths in one run are skipped |
doc_id |
Slug from folder + stem (invoices__invoice_10687); hash suffix on collision |
basename_index |
Recorded in catalog.json when the same filename appears in multiple folders |
| Hidden / junk files | ._*, .DS_Store, and hidden files are not ingested |
Run the ingest pipeline
make ingest
Custom paths:
uv run python ingest.py --data ./data --processed ./processed --index ./index
Run the MCP server
Smoke test all six tools against ingested artifacts (no client required):
make ingest # once, if processed/ and index/ are missing
make smoke # or: uv run python scripts/smoke_test_mcp.py
Start the stdio server for Cursor or another MCP client (make mcp runs test-fast first, then blocks):
make mcp
Press Ctrl+C to stop the server.
Required artifacts
make ingest must complete successfully before MCP tools work:
processed/documents.jsonlprocessed/order_index.jsonprocessed/catalog.jsonprocessed/order_comparisons.jsonindex/knowledge.db
MCP tools
Every tool returns a top-level sources[] array (citations). Use these for grounded answers.
| Tool | Purpose |
|---|---|
search_documents |
FTS keyword search; optional doc_type, order_id, period filters |
list_documents |
List documents by metadata |
get_document |
Fetch one record by doc_id; include_text=false by default |
find_document_gaps |
Gap lists from catalog (invoices_missing_po, etc.) |
compare_order_documents |
Precomputed order comparison + field citations |
get_knowledge_base_summary |
Corpus counts, inventory periods, ingest metadata |
Citation schema
Each entry in sources[] includes:
doc_id, source_path, doc_type, page, chunk_id, field, value, snippet, field_provenance, extraction_method, confidence, citation_label
Built by procurement/citations.py (build_citation, build_citation_from_chunk).
Connect Cursor
Project MCP config is committed at .cursor/mcp.json. It runs scripts/run_mcp.sh, which uses uv and loads .env (for UV_PROJECT_ENVIRONMENT on external drives).
make cursor-setup # uv sync + chmod launcher
make ingest # required once before MCP queries work
In Cursor: Developer: Reload Window, then Settings ? MCP and confirm procurement-knowledge is connected. In Agent mode, ask the agent to use procurement-knowledge tools.
To override paths locally, copy the server block to ~/.cursor/mcp.json or edit the project file.
Validate basic questions (Cursor Agent)
Use tests/prompts/test_questions.txt for a manual end-to-end check after ingest and MCP connect:
- Run
make ingestand confirm procurement-knowledge is connected (see above). - Open Agent chat and attach or paste the prompt file (
@tests/prompts/test_questions.txt). - Ask the agent to follow the file instructions: one question at a time, grounded answers with procurement-knowledge tools, and show the answer before the next question.
The prompt covers assignment-style questions (missing POs, order 10687, shipment vs invoice, contract terms, mismatches, inventory periods). Answers should cite sources[] from tool results.
| Prompt question (summary) | Primary tool(s) |
|---|---|
| Invoices missing a PO | find_document_gaps |
| PO for invoice 10248 | list_documents |
| Shipment 10603 vs invoice | compare_order_documents |
| Contract supply of goods | search_documents (doc_type=contract) |
| Documents for order 10687 | list_documents |
| Mismatches across doc types | find_document_gaps, compare_order_documents |
| Inventory reports and periods | get_knowledge_base_summary, list_documents |
Automated coverage of the same paths lives in tests/test_mcp_tools.py; make smoke exercises tools without a client.
Example queries (assignment-style)
After make ingest, automated tests in tests/test_mcp_tools.py exercise these paths. Expected results on the bundled corpus:
| Question | Tool | Expected |
|---|---|---|
| Which invoices lack a PO? | find_document_gaps(gap_type="invoices_missing_po") |
10436, 10687, 10839 |
| Documents for order 10687? | list_documents(order_id="10687") |
invoice + shipping_order |
| Does shipment match invoice for 10687? | compare_order_documents(order_id="10687") |
missing_purchase_order; invoice/shipping totals match |
| Contract supply terms? | search_documents(query="supply goods", doc_type="contract") |
Hits on TotalEnergies master contract |
| Inventory periods? | get_knowledge_base_summary() |
2016-07 ? 2018-01 |
Sample compare_order_documents("10687") summary:
Order 10687: invoice and shipping totals match; purchase order missing.
Project layout
procurement-knowledge-mcp/
test-data.zip # Corpus archive; unzip at repo root ? data/
data/ # Source documents (read-only; from test-data.zip)
processed/ # Generated JSON/JSONL (gitignored)
index/ # SQLite FTS index (gitignored)
procurement/ # Pipeline library
ingest.py # Ingest CLI
mcp_server.py # MCP server (stdio)
tests/ # pytest suite
prompts/
test_questions.txt # Cursor Agent manual validation prompt
pyproject.toml
Makefile
Testing
make test-fast # quick unit loop during development
make test # full suite with coverage
make test-integration # corpus / full-ingest tests only
make check # lint + typecheck + full suite with coverage
Current suite: 130 passed, 100% coverage on measured source (make check).
Integration tests (marked @pytest.mark.integration) run discover ? ingest on data/ and validate known orders (10687, 10248) and gap lists. make test-fast skips them for a faster feedback loop.
For interactive validation in Cursor, use tests/prompts/test_questions.txt (see Validate basic questions under Connect Cursor).
Verbose:
set -a && source .env && set +a && uv run pytest -v
Troubleshooting
| Issue | Solution |
|---|---|
uv run fails with ._ruff on external drive |
Use .env with UV_PROJECT_ENVIRONMENT on local disk; prefer make |
Processed artifacts missing |
Run make ingest |
make mcp hangs |
Normal ? stdio server waiting for client; Ctrl+C to exit |
| SQLite readonly on external drive | Ingest builds knowledge.db in a temp dir then moves it (handled in index.py) |
Known limitations
- No vector embeddings (FTS keyword search only).
- Scanned JPG invoices without OCR
order_idare excluded from the order index. - Line-item matching is best-effort regex parsing, not layout-aware extraction.
- Contract text is page-chunked plain text (no clause segmentation).
AI-assisted development
This project was developed with AI assistance (Cursor). Design decisions follow a content-primary modeling policy with explicit trade-offs for a 3?5 hour take-home scope. Validation: make check (lint, types, 100% coverage), make smoke, pytest in tests/test_mcp_tools.py, and manual Agent runs via tests/prompts/test_questions.txt.
from github.com/trussler-leveragepointdata/procurement-knowledge-mcp
Installing Procurement Knowledge
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/trussler-leveragepointdata/procurement-knowledge-mcpFAQ
Is Procurement Knowledge MCP free?
Yes, Procurement Knowledge MCP is free — one-click install via Unyly at no cost.
Does Procurement Knowledge need an API key?
No, Procurement Knowledge runs without API keys or environment variables.
Is Procurement Knowledge hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Procurement Knowledge in Claude Desktop, Claude Code or Cursor?
Open Procurement Knowledge 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 Procurement Knowledge with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
