InvoiceLens MCP
Uses Azure OpenAI for text embeddings and LLM-based invoice extraction, enabling semantic document search and structured invoice parsing, with offline fallbacks when not configured.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@InvoiceLens MCPextract the invoice data from invoice.pdf and validate the totals"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
InvoiceLens MCP
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 |
| Parses a PDF, hybrid-chunks it (tables, header fields, totals and prose handled separately), embeds the chunks with |
| Semantic search across everything ingested; returns passages with |
| Reads one invoice into a typed schema, then validates the arithmetic (line items vs subtotal, subtotal + tax vs total, tax rate, dates) |
| Share price, market cap, sector, CEO and 3 years of revenue, net income and EPS growth for a listed company (Financial Modeling Prep) |
| 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 -.-> EXTAPIBoth 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
Vendor research: market data and web search
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?" |
| Structured, dated numbers from a market-data API |
"How is the company growing? Any recent news?" |
| 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.comcurl "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-searchKnow 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 →
INFYADR); 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 |
| A |
Vendor, address, invoice no., dates, bill-to | Merged into one |
Short title line | Not a chunk; becomes the |
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/searchWith 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 itselfClaude 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 sendpdf_base64, and setINVOICELENS_ALLOW_LOCAL_PATHS=falseon any networked deployment.Auth. Bound to
127.0.0.1, the MCP SDK's DNS-rebinding protection only accepts localhostHostheaders. Bound to0.0.0.0(Docker, cloud) it doesn't, so setINVOICELENS_API_TOKENto requireAuthorization: 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
.envonly; client projects never see them.
Design decisions and safety nets
Decision | Why |
| 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; |
Exact | 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 sArea | 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 toolsinvoicelens_company_financialsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| company | Yes | Company name ('Microsoft') or ticker ('MSFT'). For non-US companies, a US/ADR ticker works best ('INFY' for Infosys). | |
| include_growth | No | Also fetch up to 3 years of revenue, net income and EPS growth. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ceo | No | |
| beta | No | |
| name | No | |
| notes | No | |
| price | No | |
| query | Yes | |
| growth | No | Most recent fiscal years first |
| sector | No | |
| symbol | Yes | |
| country | No | |
| website | No | |
| currency | No | |
| exchange | No | |
| industry | No | |
| ipo_date | No | |
| provider | Yes | |
| employees | No | |
| disclaimer | No | |
| market_cap | No | In `currency` |
| description | No | |
| resolved_by | Yes | 'ticker' if given directly, else 'name search' — confirm the match |
| retrieved_at | Yes | |
| change_percent | No | Today's price change, percent |
| is_actively_trading | No |
TDQS
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.
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.
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.
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.
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.
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_invoiceARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Path to a PDF on the server's filesystem (local/stdio use only). | |
| doc_id | No | An ingested document's doc_id (from ingest or search). | |
| feedback | No | Validation errors from a previous attempt, to guide a corrected re-read. | |
| collection | No | Index to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'. | |
| pdf_base64 | No | The PDF file content, base64-encoded. Use this when the server is remote. |
Output Schema
| Name | Required | Description |
|---|---|---|
| doc_id | No | |
| invoice | Yes | |
| filename | No | |
| extractor | Yes | |
| validation | Yes |
TDQS
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.
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.
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.
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.
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.
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_documentAIdempotent
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Path to a PDF on the server's filesystem (local/stdio use only). | |
| filename | No | Display name, e.g. 'acme_march.pdf'. Defaults to the file name or PDF title. | |
| collection | No | Index to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'. | |
| pdf_base64 | No | The PDF file content, base64-encoded. Use this when the server is remote. |
Output Schema
| Name | Required | Description |
|---|---|---|
| pages | Yes | |
| chunks | Yes | |
| doc_id | Yes | Stable id (content hash) — pass to search or extract |
| embedder | Yes | |
| filename | Yes | |
| collection | Yes | |
| chunks_by_type | Yes | |
| replaced_existing | Yes | True if this exact file was already ingested and was re-indexed |
TDQS
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.
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.
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.
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.
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.
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_documentsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| k | No | Number of passages to return. | |
| query | Yes | What to look for, in natural language, e.g. 'late payment penalty' or 'Acme invoice total'. | |
| doc_id | No | Restrict to one document. | |
| collection | No | Index to use, e.g. 'default' or a per-client name. Lowercase letters, digits, '-', '_'. | |
| chunk_types | No | Restrict to chunk types, e.g. ['totals'] for amounts or ['text'] for terms. |
Output Schema
| Name | Required | Description |
|---|---|---|
| query | Yes | |
| results | Yes | |
| collection | Yes |
TDQS
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.
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.
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.
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.
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.
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.
invoicelens_web_searchARead-only
Search the web (Tavily) and return ranked results with title, URL, snippet and publication date. Use for recent news, growth narrative and strategy; cite the URLs. Don't take share prices or market caps from snippets — use invoicelens_company_financials for numbers.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query, e.g. 'Microsoft cloud revenue growth 2026'. | |
| topic | No | 'news' for recent events, 'finance' for markets and companies. | general |
| time_range | No | Only results from this recent period. | |
| max_results | No | ||
| include_domains | No | Restrict to sites, e.g. ['reuters.com', 'sec.gov']. |
Output Schema
| Name | Required | Description |
|---|---|---|
| query | Yes | |
| topic | Yes | |
| answer | No | Provider's short summary, when requested |
| results | Yes | |
| provider | Yes | |
| retrieved_at | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, openWorldHint=true and destructiveHint=false, so safety is covered. The description still adds real value by naming the underlying engine, disclosing that results are ranked, and warning that snippet numbers are unreliable — a data-quality trait not captured by any structured field. It stops short of mentioning rate limits, latency, or query-cost behavior.
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?
Three short sentences, front-loaded with capability and return shape, followed by usage and then the numeric-data caveat. Nothing repeats the annotations or the schema, and every sentence changes what the agent does.
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 an output schema present and 80% parameter coverage, the description needn't restate fields, and it still lists them briefly. The remaining gap is minor: no word on result-count defaults/caps or what happens when a query returns nothing, though max_results and its bounds are documented in the schema.
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 80% and every meaningful parameter (query, topic, time_range, include_domains) carries its own description with examples, so the schema does the heavy lifting. The description adds no parameter-level detail — recency and domains are only implied by the "recent news" framing — which is the baseline 3 case.
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?
States a specific verb and resource ("Search the web") plus the provider (Tavily) and the exact return shape (title, URL, snippet, publication date). It also distinguishes itself from a sibling by name when numbers are needed, so an agent can pick it without opening either 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?
Gives positive guidance ("Use for recent news, growth narrative and strategy") and an explicit exclusion with a named alternative ("Don't take share prices or market caps from snippets — use invoicelens_company_financials"). The cite-the-URLs instruction is an actionable usage rule rather than filler.
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.
5 tool updates
v0.1.0- First observed
invoicelens_company_financials - First observed
invoicelens_extract_invoice - First observed
invoicelens_ingest_document - First observed
invoicelens_search_documents - First observed
invoicelens_web_search
TDQS
Scored across 5 tools
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.
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.
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.
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
Related MCP Connectors
Primary-source SEC filing intelligence and financial/disclosure reconciliation for AI agents.
Real SEC, 13F, insider, congress & macro data your AI agent can cite. Hosted MCP, 24 tools.
Live AI-native web search with citations. One tool for every MCP client. Flat per-request pricing.
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to search, retrieve, and answer grounded questions over procurement documents (invoices, purchase orders, contracts, etc.) using hybrid SQL and vector retrieval.-
- FlicenseNot gradedqualityDmaintenanceEnables 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.-
- AlicenseNot gradedqualityBmaintenanceMCP 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 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables 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.-