Skip to main content
Glama

InvoiceLens MCP

tests

An MCP server for invoice PDFs and vendor research. It exposes five tools that any MCP client can use: a LangGraph agent in another project, Claude Desktop, or Claude Code.

Tool

What it does

invoicelens_ingest_document

Parses a PDF, hybrid-chunks it (tables, header fields, totals and prose handled separately), embeds the chunks with text-embedding-3-large and stores them in FAISS

invoicelens_search_documents

Semantic search across everything ingested; returns passages with doc_id, filename, page and score

invoicelens_extract_invoice

Reads one invoice into a typed schema, then validates the arithmetic (line items vs subtotal, subtotal + tax vs total, tax rate, dates)

invoicelens_company_financials

Share price, market cap, sector, CEO and 3 years of revenue, net income and EPS growth for a listed company (Financial Modeling Prep)

invoicelens_web_search

Current web results with source URLs and dates: news, growth, strategy (Tavily)

The same operations are also exposed as a REST API (/ingest, /search, /extract, /company-financials, /web-search) from the same process, backed by the same index.

Architecture

flowchart TB
    subgraph clients[Clients]
        LG[LangGraph agent<br/>any project]
        CD[Claude Desktop / Code<br/>stdio]
        HC[HTTP client<br/>curl, Swagger UI]
    end
    subgraph server[InvoiceLens server]
        MCP[MCP server · FastMCP<br/>5 tools, stdio + HTTP]
        API[REST API · FastAPI<br/>/ingest /search /extract …]
        SVC[InvoiceLensService<br/>single shared core]
        ING[Ingestion<br/>parse → hybrid chunk → embed]
        RET[Retriever<br/>query → top-k]
        EXT[Extractor<br/>LLM + rules fallback → validator]
        MKT[Market data + web search<br/>cached, actionable errors]
        FAISS[(FAISS IndexFlatIP<br/>per collection, on disk)]
        META[(chunk + document<br/>metadata, original PDFs)]
    end
    AOAI[Azure OpenAI<br/>embedding-3-large · gpt-4o]
    EXTAPI[FMP · Tavily]
    LG --> MCP
    CD --> MCP
    HC --> API
    MCP --> SVC
    API --> SVC
    SVC --> ING & RET & EXT & MKT
    ING --> FAISS & META
    RET --> FAISS
    EXT --> META
    ING & RET & EXT -.-> AOAI
    MKT -.-> EXTAPI

Both interfaces are thin adapters over one InvoiceLensService, so they can't drift apart. A test asserts that MCP and REST return identical results for the same call.

The agent graph (agent/)

flowchart LR
    S([start]) --> A[agent<br/>LLM + 5 MCP tools]
    A -- tool calls --> T[tools<br/>MCP ToolNode]
    A -- no tool calls --> E([answer, cited])
    T -- extract_invoice --> V{{validate<br/>plain code}}
    T -- other tools --> A
    V -- valid, or retry with errors --> A
    V -- failed twice --> R[flag for review] --> E2([needs human review])

The validate node is deterministic code, not an LLM. When an extraction fails the arithmetic checks, it sends the exact errors back to the agent ("Line items sum to 5,700.00 but subtotal is 5,900.00"). The agent then re-extracts with that feedback. If the second attempt also fails, the graph stops and flags the invoice for human review, rather than letting the agent present numbers that don't add up. Some invoices really are wrong; samples/initech_* is one.

Related MCP server: doc-intel MCP server

Two tools look outward, so an agent can go from "who sent this invoice?" to "what do we know about them?"

Question

Tool

Why this one

"What's Microsoft's market cap / share price / revenue growth?"

invoicelens_company_financials

Structured, dated numbers from a market-data API

"How is the company growing? Any recent news?"

invoicelens_web_search

Recent context with source URLs

Numbers deliberately don't come from web search: snippets can be days old or quote the wrong currency. The tool descriptions and the agent's system prompt both steer the model to the right tool.

Setup. Both keys are optional; without one, its tool returns a clear "set INVOICELENS_…_API_KEY" error and everything else keeps working.

INVOICELENS_TAVILY_API_KEY=tvly-...      # https://app.tavily.com
INVOICELENS_FMP_API_KEY=...              # https://site.financialmodelingprep.com
curl "http://127.0.0.1:8765/company-financials?company=Microsoft"
curl -H 'content-type: application/json' -d '{"query":"Microsoft cloud growth","topic":"news","time_range":"month"}' \
     http://127.0.0.1:8765/web-search

Know the free tiers (as of October 2026):

  • FMP: 250 calls a day, and US-listed securities only. Non-US companies resolve through a US listing where one exists (Infosys → INFY ADR); otherwise they need a paid plan. Name searches prefer US exchanges for this reason. Lookups are cached for 10 minutes (one company costs 2–3 calls), and if the growth endpoint isn't in your plan, the profile still returns with a note.

  • Tavily: 1,000 credits a month on the free tier. Each search uses one credit (search_depth: basic).

  • Private companies, like the fictional vendors in samples/, aren't listed. The financials tool says so and points the agent to web search instead. To demo the market tools, ask about a real US-listed company.

Market data is returned with its source, retrieved_at timestamp and a "not investment advice" note, and the API key travels in a header, never in the URL.

Hybrid chunking

A pure fixed-size splitter cuts invoices in the worst places: through a line-item table, or between Invoice Number: and its value. A pure structural splitter makes wildly uneven chunks. InvoiceLens does both, in order.

1. Structure. Each page's elements are classified in reading order:

Element

Becomes

Table

Its own chunk, as a markdown table. Never split mid-row; oversized tables split by rows with the header repeated in each piece

Subtotal / VAT / Total block

A totals chunk, which is what "how much" queries need

Vendor, address, invoice no., dates, bill-to

Merged into one header chunk, so matching metadata stays together

Short title line

Not a chunk; becomes the section of the chunks after it

Paragraphs

Go to step 2

2. Size. Prose is packed into windows of at most 512 tokens with 64 tokens of sentence-level overlap. Sentences are never cut unless a single sentence exceeds the window.

Every chunk is embedded with a contextual header (Document: acme.pdf | Page 1 | totals | Section: …), so a terms paragraph is still findable by its vendor.

On the samples, every invoice produces header → table → totals → text. The Northwind terms page (about 180 tokens) is one text chunk under Section: Terms and Conditions; longer documents are windowed with overlap (see test_long_prose_is_windowed_with_overlap).

Quick start

pip install -e ".[agent,dev]"
cp .env.example .env              # add Azure OpenAI details for real embeddings + LLM extraction
invoicelens-serve                 # REST + MCP on http://127.0.0.1:8765  (Swagger at /docs)
curl -F file=@samples/northwind_logistics_2026-04.pdf http://127.0.0.1:8765/ingest
curl -H 'content-type: application/json' -d '{"query":"late payment penalty"}' http://127.0.0.1:8765/search

With no keys set, InvoiceLens runs fully offline using a hashing embedder and a rule-based extractor. That's how the tests and CI run, and why the server always starts. The hashing embedder is lexical, not semantic, so use Azure OpenAI for real retrieval quality. /health reports which providers are active.

Use it from another project

That project needs no InvoiceLens code. It only needs the server's URL and langchain-mcp-adapters:

from langchain.agents import create_agent          # pip install langchain langchain-mcp-adapters
from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "invoicelens": {
        "url": "http://127.0.0.1:8765/mcp",
        "transport": "streamable_http",
        # "headers": {"Authorization": "Bearer <INVOICELENS_API_TOKEN>"},
    }
})
tools = await client.get_tools()   # the five invoicelens_* tools
agent = create_agent(llm, tools)

To get the self-correcting validation loop, copy agent/graph.py instead and call build_graph(llm, tools). Or run the bundled one:

python -m agent.run "What is the total on the Acme invoice?"
python -m agent.run --stdio "Which invoice charges a late payment penalty?"   # launches the server itself

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "invoicelens": { "command": "invoicelens-mcp", "args": ["--transport", "stdio"] }
  }
}

Things to know when sharing one server

  • One index, many clients. Everything ingested is searchable by every client. Pass collection (e.g. "client-a") to keep separate indexes.

  • Sending files. Over stdio, tools can take a server-side path. A remote server can't see your disk, so send pdf_base64, and set INVOICELENS_ALLOW_LOCAL_PATHS=false on any networked deployment.

  • Auth. Bound to 127.0.0.1, the MCP SDK's DNS-rebinding protection only accepts localhost Host headers. Bound to 0.0.0.0 (Docker, cloud) it doesn't, so set INVOICELENS_API_TOKEN to require Authorization: Bearer … on REST and MCP. For multi-user production, put it behind an OAuth gateway such as Azure API Management with Entra ID.

  • Keys stay on the server. Azure OpenAI credentials live in the server's .env only; client projects never see them.

Design decisions and safety nets

Decision

Why

doc_id is a content hash

Re-ingesting the same file replaces it instead of duplicating its chunks

Index manifest records the embedding model

Mixing vectors from two models silently ruins search; the store refuses with an explicit error

Extractor reports values as printed

Silently "fixing" a wrong invoice would hide a real problem from whoever pays it; the validator flags it instead

LLM extraction falls back to rules on failure

A network blip degrades quality, not availability; extractor in the response says which one ran

Exact IndexFlatIP over normalised vectors

Exact cosine search, fast well past demo scale (~100k chunks); swap for HNSW or a managed store beyond that

Tool errors say what to do next

The agent reads them first: "No document 'x'. Use invoicelens_search_documents to find doc_ids"

Version notes

The project pins MCP SDK 1.x (mcp>=1.30,<2) on purpose. MCP SDK 2.x renamed FastMCP to MCPServer and changed internals, and langchain-mcp-adapters (the LangGraph bridge, 0.3.2 at the time of writing) doesn't support 2.x yet. Pinning 1.x keeps the server usable from any LangGraph project today.

Tests

pytest -q          # 74 tests, offline, ~8 s

Area

What's covered

Chunking

tables never split mid-row (header repeated), prose windows with overlap, nothing dropped, vendor kept in the header chunk

Store

re-ingest replaces, persistence across restarts, filters, collection isolation, refuses mixed embedding models

Extraction

all sample invoices field by field, the planted Initech error, each validator rule, date formats, LLM fallback

Interfaces

MCP tool schemas and annotations, actionable errors, REST endpoints, MCP–REST parity, bearer auth, DNS-rebinding protection

Agent

the real MCP server with a scripted LLM: a valid invoice goes straight to the answer; an invalid one gets the validator's errors fed back, then is flagged after two failures

Providers

Azure/OpenAI paths against SDK-shaped fakes: batching, dimension check, feedback in the prompt

External data

Tavily and FMP against mocked HTTP: request shape, US-listing resolution, plan-restricted growth, caching, every error path, missing keys as tool errors

Project layout

src/invoicelens/
  parsing.py      PDF -> text blocks + tables, in reading order
  chunking.py     hybrid chunker
  embeddings.py   Azure OpenAI / OpenAI text-embedding-3-large, offline hashing embedder
  store.py        FAISS + metadata per collection, persisted
  extraction.py   LLM structured outputs, rule-based fallback
  validation.py   arithmetic and date checks
  service.py      the shared core
  external.py     Tavily web search, FMP market data
  mcp_server.py   FastMCP: 5 tools, stdio + Streamable HTTP
  api.py          FastAPI + MCP mounted at /mcp
agent/            LangGraph agent (an MCP client; imports nothing from invoicelens)
samples/          synthetic invoices + their generator
tests/

Sample data

samples/ holds four synthetic invoices generated by samples/generate_samples.py. No real company data is included. Initech's stated subtotal is deliberately wrong, and Northwind has a terms-and-conditions page.

Available Tools

5 tools
invoicelens_company_financialsA
Read-onlyIdempotent

Share price, market capitalisation, sector/industry, CEO, employees and annual growth (revenue, net income, EPS; fractions, 0.12 = +12%) for a publicly listed company, from Financial Modeling Prep.

Check resolved_by: if it was found by name search, confirm name matches what was meant. Private companies aren't covered. Quote retrieved_at with the numbers; prices may be delayed. For context or news, use invoicelens_web_search.

NameRequiredDescriptionDefault
companyYesCompany name ('Microsoft') or ticker ('MSFT'). For non-US companies, a US/ADR ticker works best ('INFY' for Infosys).
include_growthNoAlso fetch up to 3 years of revenue, net income and EPS growth.

Output Schema

NameRequiredDescription
ceoNo
betaNo
nameNo
notesNo
priceNo
queryYes
growthNoMost recent fiscal years first
sectorNo
symbolYes
countryNo
websiteNo
currencyNo
exchangeNo
industryNo
ipo_dateNo
providerYes
employeesNo
disclaimerNo
market_capNoIn `currency`
descriptionNo
resolved_byYes'ticker' if given directly, else 'name search' — confirm the match
retrieved_atYes
change_percentNoToday's price change, percent
is_actively_tradingNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds substantive traits: upstream source (Financial Modeling Prep), staleness caveat ('prices may be delayed'), a citation obligation ('Quote retrieved_at'), coverage limits (public companies only), and the numeric encoding of growth (fractions, 0.12 = +12%). None of that is inferable from the schema or annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the returned payload, then the operational caveats (resolved_by, retrieved_at, delay) and the sibling pointer. Dense but every sentence carries usable instruction; only the long field enumeration slightly delays the reader.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Two simple parameters with full schema descriptions, plus an output schema, so the description need only cover sourcing, staleness and coverage limits — all of which it does, including the fraction convention that the output schema alone would leave unclear.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description goes further by disambiguating growth values as fractions (0.12 = +12%) and clarifying that growth is annual/multi-year revenue, net income and EPS. It does not restate the ticker-vs-name or ADR guidance already in the schema, which is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Enumerates the exact data returned (share price, market cap, sector/industry, CEO, employees, revenue/net income/EPS growth) for a publicly listed company, so the resource is unambiguous. It also names the non-overlapping sibling (invoicelens_web_search) for context/news, letting an agent pick it apart from the document-oriented siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit exclusions ('Private companies aren't covered') and an explicit alternative ('For context or news, use invoicelens_web_search'), plus an operational rule to verify `resolved_by`/`name` after a name-based lookup. That is when-to-use, when-not-to-use, and the alternative in one paragraph.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

invoicelens_extract_invoiceA
Read-onlyIdempotent

Extract one invoice into structured fields (vendor, invoice number, dates, currency, line items, subtotal, tax, total) and check them: line items vs subtotal, subtotal + tax vs total, tax rate, dates.

validation.valid = false means the numbers don't add up — either a misread (retry with feedback) or an invoice that is genuinely wrong. Values are reported as printed, never silently corrected.

NameRequiredDescriptionDefault
pathNoPath to a PDF on the server's filesystem (local/stdio use only).
doc_idNoAn ingested document's doc_id (from ingest or search).
feedbackNoValidation errors from a previous attempt, to guide a corrected re-read.
collectionNoIndex to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'.
pdf_base64NoThe PDF file content, base64-encoded. Use this when the server is remote.

Output Schema

NameRequiredDescription
doc_idNo
invoiceYes
filenameNo
extractorYes
validationYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, and the description still adds substantive behavior: which cross-checks run (line items vs subtotal, subtotal + tax vs total, tax rate, dates) and the key non-obvious guarantee that values are reported as printed and never silently corrected. The interpretation of validation.valid=false as either a misread or a genuinely wrong invoice tells the agent how to react, not just what happened.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and outputs, then the validation semantics, with no filler sentences. The awkward line break mid-sentence ('The check covers:') slightly hurts readability but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape detail is not required, yet the description still explains the one field an agent must interpret (validation.valid). Combined with the retry loop, it is sufficient to call and recover. Multiple mutually exclusive input sources are only handled by the schema, not the prose, which is the remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already explains path, doc_id, feedback, collection and pdf_base64, including the local-vs-remote distinction. The description reinforces `feedback` as the retry channel but adds no format or constraint detail beyond it; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Extract one invoice into structured fields') and enumerates the fields produced, so an agent knows exactly what comes back. The single-invoice scope also distinguishes it implicitly but clearly from invoicelens_search_documents and invoicelens_ingest_document.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete conditional workflow: when validation.valid is false, retry with `feedback`. That is real when-to-use guidance for the retry path. It stops short of naming alternatives or stating prerequisites (e.g. that doc_id requires a prior ingest), so it is clear context rather than full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

invoicelens_ingest_documentA
Idempotent

Parse a PDF, split it with hybrid chunking (tables, header fields, totals and text kept as separate chunks), embed the chunks and store them in the FAISS index.

Ingesting the same file again replaces its previous version (doc_id is a content hash). Returns the doc_id plus chunk counts by type.

NameRequiredDescriptionDefault
pathNoPath to a PDF on the server's filesystem (local/stdio use only).
filenameNoDisplay name, e.g. 'acme_march.pdf'. Defaults to the file name or PDF title.
collectionNoIndex to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'.
pdf_base64NoThe PDF file content, base64-encoded. Use this when the server is remote.

Output Schema

NameRequiredDescription
pagesYes
chunksYes
doc_idYesStable id (content hash) — pass to search or extract
embedderYes
filenameYes
collectionYes
chunks_by_typeYes
replaced_existingYesTrue if this exact file was already ingested and was re-indexed

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare non-read-only, idempotent, non-destructive, closed-world. The description adds genuinely new behavioral facts: re-ingestion overwrites the prior version, doc_id is a content hash (which explains the idempotency), and what the response contains. It omits cost/time characteristics of embedding, which is the main operational unknown.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short paragraphs, front-loaded with the pipeline and followed by the replace semantics. The trailing 'Returns the doc_id plus chunk counts by type' is slightly redundant given an output schema exists, but it is one short clause.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations covering safety/idempotency, a full output schema, and 100% parameter documentation, the description only needed to supply the write semantics and overwrite behavior, which it does. Missing only environmental notes such as index persistence or expected latency.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so path, filename, collection and pdf_base64 are already fully documented with defaults and constraints. The description adds no parameter-level guidance, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb chain (parse, split, embed, store) on a specific resource (PDF into a FAISS index) with the chunking strategy named. It is immediately distinguishable from retrieval siblings such as search_documents and from extract_invoice, which does not write to the index.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the ingestion use case and covers the re-ingest case ('Ingesting the same file again replaces its previous version'), but never says when to prefer this over extract_invoice or which of path/pdf_base64 to choose in a given deployment. Usage is inferable rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

invoicelens_search_documentsA
Read-onlyIdempotent

Semantic search over ingested invoices. Returns the top-k passages with doc_id, filename, page, chunk type and similarity score — cite filename and page in answers. Use the doc_id with invoicelens_extract_invoice to read a document's fields precisely.

NameRequiredDescriptionDefault
kNoNumber of passages to return.
queryYesWhat to look for, in natural language, e.g. 'late payment penalty' or 'Acme invoice total'.
doc_idNoRestrict to one document.
collectionNoIndex to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'.
chunk_typesNoRestrict to chunk types, e.g. ['totals'] for amounts or ['text'] for terms.

Output Schema

NameRequiredDescription
queryYes
resultsYes
collectionYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive and closed-world, so safety is covered. The description adds behavior the annotations lack: the ranked-passage result shape, the presence of a similarity score, and an explicit citation obligation ("cite filename and page in answers"). It says nothing about empty-result behavior or cost/latency limits, keeping it below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the operation and result shape, then the workflow handoff and citation rule. No filler, and every clause carries actionable information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and the input schema is fully described, so the description does not need to enumerate fields exhaustively. It still covers purpose, result content, and the follow-up tool, which is everything an agent needs to select and call this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so k, query, doc_id, collection and chunk_types are all already documented with examples. The description's only parameter-adjacent signal is "top-k," which restates the k parameter without adding format or default guidance. Baseline 3 applies when the schema carries the semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Semantic search over ingested invoices") plus the retrieval granularity (top-k passages). It also implicitly distinguishes itself from invoicelens_web_search by scoping to the ingested corpus and from invoicelens_extract_invoice by pointing at it for precise field extraction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear next-step rule: take the returned doc_id into invoicelens_extract_invoice when exact fields are needed, which effectively tells the agent when this tool is the wrong final step. It does not state exclusions relative to invoicelens_web_search or invoicelens_company_financials, so the routing is clear but incomplete.

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.

  1. 5 tool updatesv0.1.0
    • First observedinvoicelens_company_financials
    • First observedinvoicelens_extract_invoice
    • First observedinvoicelens_ingest_document
    • First observedinvoicelens_search_documents
    • First observedinvoicelens_web_search

TDQS

A4.2/5.0

Scored across 5 tools

Disambiguation4/5

Most tools are clearly distinct: ingest stores documents, extract reads structured fields with validation, company_financials pulls market data, and web_search queries the internet. The only potential confusion pairs are ingest vs. extract (both document-processing) and search_documents vs. web_search, but the descriptions explicitly delineate their roles and cross-reference each other.

Naming Consistency4/5

All names are snake_case with a consistent invoicelens_ prefix, and the first three follow a verb_noun pattern (ingest_document, extract_invoice, search_documents). The last two (company_financials, web_search) are noun-based rather than verb-based, a minor deviation from the otherwise predictable scheme.

Tool Count5/5

Five tightly scoped tools cover the invoice-analysis workflow from ingestion through extraction, search, and external enrichment. Each tool has a clear, non-redundant job, so nothing feels padded or missing at this scale.

Completeness4/5

The core lifecycle is covered: ingest, extract with validation, semantic search, plus financial and web enrichment. Minor gaps exist — no explicit list-documents or delete-document operation, and no batch or export tool, though re-ingesting replaces duplicates and search partly substitutes for listing.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to extract structured data from PDFs with confidence scores and provenance, and to search, review, and correct documents via MCP tools, resources, and prompts.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that gives AI agents read access to subscription/SaaS invoice history, ingested from Gmail and stored in Postgres and MongoDB, with tools for listing invoices, retrieving PDFs, and summarizing spend.
    175 npm
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to parse and analyze PDF invoices, including text, field, and table extraction, OCR for scanned documents, ZUGFeRD support, compliance validation, and batch processing via MCP.
    -