Footnote
Provides a built-in SQLite sample ERP/finance dataset for the server's typed operations, enabling evidence-backed query results when no external DATABASE_URL is 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., "@FootnoteHow much does Bergmann owe?"
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.
Footnote
An MCP server that prevents hallucinated numbers when AI answers questions over ERP and finance data. Every figure in an answer traces back to the database rows that produced it: clickable at read time, exportable for an auditor, and tamper-evident.
Works with any MCP client: Claude Desktop, Cursor, Microsoft Agent Framework, Semantic Kernel (.NET), LangChain, or your own loop.
An MCP server, a provenance store, and a demo UI showing what the combination looks like.
The quickest way to try it:
// claude_desktop_config.json
{ "mcpServers": {
"footnote": {
"command": "npx",
"args": ["-y", "@brick-byte/footnote"],
"env": {
"PROVENANCE_DB": "/path/to/provenance.db",
"PROVENANCE_ACTOR": "your-name",
// Omit for the built-in SQLite sample data.
"DATABASE_URL": "postgres://user:pass@localhost:5432/erp"
}
} } }Or run the demo UI locally:
npm install
npm run demo # http://localhost:4711
npm test # 26 tests, including the invariants belowNode 22.5+ required (uses the built-in node:sqlite, so there is no database to install).
Postgres is opt-in. The same schema, seed and query set run against it, and the same suite has to pass on both:
npm run db:up # Postgres 17 in Docker, seeded from migrations/001_init.sql
npm run test:pg # the identical 26 tests, against Postgres
npm run demo:pg # the demo, against Postgres
The problem
Language models produce plausible continuations, not true ones. Point one at a business database and it will confidently report a number that no query produced — and the fabricated figure is indistinguishable from the real one, because both come out of the same machinery.
The usual answers don't hold up here. Uncertainty signals catch the fuzzy cases, not the fluent ones. A verifier built from the same model can confirm a fabrication as easily as catch it. And in finance, inventory or procurement, "the model said so" is not an answer anyone can act on.
What does work is structural: never let the model produce the number.
Related MCP server: Semantic BI MCP
The architecture
question
│
▼
model ──────────► picks an operation from a catalog and supplies typed args
│ (it cannot write SQL, cannot invent an operation)
▼
contract layer ─► validates params, resolves entities against the database,
│ rejects anything that does not exist
▼
deterministic ──► executes the query; SQL does all aggregation
core returns values + the evidence behind each one
│
▼
provenance ─────► appends a hash-chained record, server-side,
store where the model cannot influence it
│
▼
host UI ────────► fills slots from structured output and renders each
claim as a clickable citationThe model handles translation and phrasing — the two things it is reliable at. Numbers come from the system of record.
What it enforces
These are tests, not guidelines. npm test fails if any of them breaks.
No number is generated. Values are copied verbatim from an operation's structured output into template slots. A test asserts every monetary figure in an answer is covered by a claim with evidence behind it, and that the stated total equals the sum of the records it cites.
Entities are validated before any query runs. A fabricated customer produces a recoverable error listing real alternatives — not a silent empty result the model narrates around.
Aggregation happens in SQL. The model chooses which aggregation to run, never what it equals.
Empty results are handled in code. This is the single biggest fabrication trigger: hand a model an empty result set mid-sentence and it invents something to finish the sentence. Operations set an explicit emptyReason instead.
Unroutable questions are refused. "What will revenue be next quarter?" maps to no operation, so no answer is produced. Refusal is a first-class outcome, not a failure.
Provenance is written server-side. The model never reports its own provenance. Provenance that depends on the model choosing to cite is worthless.
The log is tamper-evident. Each record carries the hash of its predecessor. npm run verify walks the chain and reports the first record that was modified after it was written.
Query ordering is a tested contract. listCustomers must return record order, because entity extraction takes the first match. Sorting it by name instead produces the failure this project exists to prevent: a fluent sentence, a real figure, correctly cited to real rows, about the wrong company. There is a test for exactly that.
Interpretations, not just figures
The harder case is judgement. "Bergmann is at risk" is a conclusion with no row to check it against — and an ungrounded interpretation is more dangerous than an ungrounded number, because it reads as insight and survives review.
The answer here states the conclusion and then lays out each premise as a separately cited fact:
Bergmann Logistik GmbH shows two independent risk signals.
This is an interpretation — the underlying facts are:
· 43.805,00 € overdue across 4 invoices, oldest 104 days past due
· 2 open support tickets, 1 high severity
· agreed payment terms of 30 days, account owned by Ana Kovač
The conclusion is the model's. Every fact above is a record you can open.A human can disagree with the reasoning while trusting the inputs.
Provenance record format
{
"id": "…", "sequence": 1, "timestamp": "2026-08-14T09:12:04.000Z",
"actor": { "id": "zoran", "role": "controller" },
"question": "How much does Bergmann owe?",
"answer": "Bergmann Logistik GmbH is 43.805,00 € overdue across 4 invoices…",
// Each claim indexes a span of the answer and names its evidence.
"claims": [
{
"text": "43.805,00 €",
"span": { "start": 25, "end": 36 },
"evidenceIds": ["invoice:8812", "invoice:8834", "invoice:8901", "invoice:8977"],
"origin": "structured" // never "generated" for a value
}
],
// The query, so the result is reproducible.
"operations": [
{ "name": "getOverdueInvoices",
"arguments": { "customer": "Bergmann", "asOfDate": "2026-08-14" },
"query": { "sql": "SELECT …", "params": ["2026-08-14", 4471, "2026-08-14"] },
"durationMs": 0.42 }
],
// Field values, not just IDs — the export stands alone if the source moves on.
"evidence": [
{ "id": "invoice:8812", "type": "invoice", "recordId": "8812",
"uri": "erp://invoice/8812",
"fields": { "number": "INV-2026-8812", "due_on": "2026-05-02",
"outstanding": "18.450,00 €", "days_overdue": 104 } }
],
"previousHash": "0000…", "hash": "9aa2ba…"
}origin is the field that matters. structured means the value was copied from an operation's output. generated means a model produced it — which for a number is exactly what this project exists to prevent.
MCP details
The server uses the parts of the protocol that carry this work:
Feature | Use |
| The model gets a typed catalog, not a query language |
| Values return as validated JSON beside the prose, so figures never have to survive a round trip through generated tokens |
| Every record touched comes back as an |
| Record links are marked user-facing |
| Carries the provenance record ID and hash so a host can fetch the full trace |
| A fabricated entity is a recoverable tool error the model can correct, not a protocol failure |
What MCP does not give you, and what this repo adds: nothing in the protocol binds this claim to that evidence. The protocol hands you links; the claim-to-evidence mapping and its rendering live in the host. That binding is the actual product.
Run standalone over stdio:
npm run mcpThe claude_desktop_config.json entry is at the top of this README.
Running from source (for development)
{ "mcpServers": {
"footnote": {
"command": "npx",
"args": ["tsx", "--experimental-sqlite", "/path/to/footnote/src/mcp-server.ts"],
"env": { "PROVENANCE_DB": "/path/to/provenance.db", "PROVENANCE_ACTOR": "your-name" }
} } }What you get over plain MCP, and what needs host integration
Any MCP client — Claude Desktop, Microsoft Agent Framework, Semantic Kernel, LangChain, your own loop — can call this server today. What that gives you:
a fixed catalog of typed operations; the model cannot write SQL or invent one
entity validation, so a fabricated customer is an error rather than an empty result
aggregation in SQL, never in the model
refusals instead of improvisation when a question maps to no operation
resource_linkentries identifying every record toucheda hash-chained, server-side provenance log of every call
What it does NOT give you: clickable claim-level citations. Over plain MCP the framework receives structuredContent and resource links, and then its own model composes the answer — at which point the binding between a specific claim and its evidence is gone, and the numbers pass back through generated tokens.
Preserving that binding is host-side work, because nothing in the protocol carries it. src/web.ts and src/public/index.html are the reference implementation: claim spans, citation chips, record resolution. Roughly 200 lines. Port that to your host and you get the other half.
Try these in the demo
Question | What it shows |
How much does Bergmann owe? | Every figure clickable to the invoice behind it |
Is Bergmann Logistik at risk? | An interpretation with separately cited premises |
Show me the receivables aging | Portfolio aggregation, computed in SQL |
How much does Acme Corporation owe? | Fabricated entity → refusal with real alternatives |
What will revenue be next quarter? | Unroutable → refusal rather than improvisation |
Toggle "show what this looks like when the model produces the numbers itself" for the contrast: identical formatting, fluent prose, every figure wrong, nothing in the output marking which.
Layout
migrations/001_init.sql schema + seed, shared by both backends and Docker
src/database.ts the Db seam — SqliteDb and PostgresDb behind one interface
src/db.ts opens the ERP database, applies the migration
src/operations.ts the catalog — the model's entire callable surface
src/compose.ts slot-filling; claims and spans are emitted here
src/provenance.ts append-only hash-chained store, export, verification
src/mcp-server.ts MCP server over stdio
src/web.ts demo host application
src/verify.ts npm run verify / npm run export
test/run.ts 26 tests covering the invariants above, on both backends
docker-compose.yml Postgres 17, seeded from the same migrationOnly operations.ts writes ERP SQL, in one dialect, with ? placeholders.
PostgresDb rewrites those to $1..$n, and the single expression that genuinely
differs between the engines — whole days between two dates, julianday() on
SQLite, date subtraction on Postgres — is named daysBetween on the Db
interface rather than inlined. That is the one place a port can silently change
a number, so it is isolated and the suite asserts the same day counts on both.
Status and limits
v0, and honest about scope.
You cannot add provenance to someone else's AI. If the answer comes from SAP Joule or Dynamics Copilot, you have no access to their retrieval pipeline or rendering. This approach applies to AI layers you build yourself over business data.
Vendor citations exist but stop short. As of August 2026, SAP, Microsoft, NetSuite and Salesforce all ship citations that resolve to knowledge content — help articles, PDFs, documentation. None ships a general mechanism binding a figure to the transaction behind it. Numeric answers are the weakest link everywhere.
The demo's intent routing is deliberately naive keyword matching, so the project runs with no API key. In a real deployment the model emits the tool call and this code still fills the slots. The architecture is the point, not the routing.
Writes are out of scope. In an ERP a fabricated read is embarrassing; a fabricated write corrupts a ledger. Writes belong behind a proposal → validation → confirmation flow.
Retention, access control, and a real deep-link resolver for a specific ERP are the last mile — and the last mile is where the actual product lives.
Prior art worth knowing
OpenTelemetry GenAI semantic conventions — spans for GenAI clients and MCP. Early; stops well above record-level provenance.
OpenLineage — dataset and job lineage. Same idea one layer down, for tables rather than claims.
LLM observability — Langfuse, LangSmith, Arize, Datadog. Tracing for engineers debugging apps, not provenance for the person reading the answer.
License
MIT.
Available Tools
6 toolsgetCustomerSummaryCustomer master data and balanceARead-onlyIdempotent
Returns master data for one customer plus their total open balance and open ticket count. Use this for "tell me about customer X" questions.
| Name | Required | Description | Default |
|---|---|---|---|
| customer | Yes | Customer name or numeric ID. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=false, so the safety profile is covered. The description adds the shape of the payload (balance plus ticket count in one call) but says nothing about behavior when the customer is ambiguous or not found, or about the cost of the aggregate lookup.
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 sentences with zero filler; the returned payload is front-loaded and the usage cue follows. Every clause carries 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?
With no output schema, the description does its job by naming what comes back (master data, open balance, open ticket count). It leaves "master data" unenumerated and omits not-found behavior, but for a one-parameter read tool with clear annotations this is nearly sufficient.
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% and the single parameter already documents "Customer name or numeric ID." The description adds no matching rules, partial-name behavior, or case sensitivity beyond the schema, so the baseline 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 (returns) and resource (master data for one customer) and enumerates the extra payload: total open balance and open ticket count. That composite scope implicitly separates it from the narrower siblings (getOpenTickets, getPaymentHistory), though no sibling is named explicitly.
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?
"Use this for 'tell me about customer X' questions" gives a concrete selection trigger, which is more than most definitions offer. It stops short of stating when to prefer the narrower siblings (e.g. detailed invoice history) or any prerequisites such as needing an exact customer identifier.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
getOpenTicketsOpen support tickets for a customerARead-onlyIdempotent
Returns unresolved support tickets for one customer. Use this when assessing account risk or when a question asks why a customer might be unhappy or withholding payment.
| Name | Required | Description | Default |
|---|---|---|---|
| customer | Yes | Customer name or numeric ID. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnly, idempotent and closed-world behavior, so the description only needs to add context. It does add real signal by scoping results to 'unresolved' tickets, meaning resolved ones are excluded, but says nothing about volume, ordering, or what happens for an unknown customer.
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 sentences, with the resource and scope front-loaded before the usage advice. Every clause carries information and nothing is redundant.
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?
For a one-parameter read tool with full schema coverage, annotations and no output schema, this covers what an agent needs to decide and invoke. The only minor gap is that 'unresolved' is never pinned to a status definition and the zero-results case is unaddressed.
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%, and the schema already documents the single 'customer' parameter as a name or numeric ID. The description only reinforces the single-customer scope, adding no format or lookup guidance beyond the schema; baseline 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 and resource ('Returns unresolved support tickets') with an explicit scope of 'one customer', which is enough for an agent to distinguish it from invoice- and payment-oriented siblings. It doesn't explicitly name or contrast with any sibling, so it stops short of a 5.
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 concrete triggering conditions ('assessing account risk', 'why a customer might be unhappy or withholding payment'), which is stronger than implied usage. It offers no when-not-to-use guidance and doesn't name the alternative tools for related questions, so it is not a full routing instruction.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
getOverdueInvoicesOverdue invoices for a customerARead-onlyIdempotent
Returns every invoice for one customer that is past its due date and not fully paid, as of a given date, with the outstanding total. Aggregation is done in SQL. Use this for any question about what a customer owes.
| Name | Required | Description | Default |
|---|---|---|---|
| asOfDate | Yes | Evaluate overdue status as of this date. | |
| customer | Yes | Customer name or numeric ID. Must resolve to a real customer. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and closed-world, so the safety profile is covered; the description adds real value by disclosing that aggregation happens in SQL (no client-side summing or pagination caveats) and that the response carries an outstanding total. It does not state what happens if the customer fails to resolve, which is the one behavioral gap.
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 sentences, front-loaded with what is returned and under which conditions, with the usage hint last. 'Aggregation is done in SQL' is a mild implementation detail but does convey that totals are precomputed rather than left to the caller.
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?
For a two-parameter read with no output schema, the description covers the filter semantics and signals the aggregate field, which is enough for correct invocation. It leaves the per-invoice return shape unstated, which is acceptable given the tool's narrow purpose.
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 both parameters are already documented, including the date pattern and the 'must resolve to a real customer' constraint. The description only restates 'as of a given date' and 'one customer' without adding format or failure semantics, so baseline 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 and resource ('Returns every invoice for one customer') plus precise scope qualifiers: past due date, not fully paid, as of a given date. The scope is distinguishable from getPaymentHistory or getReceivablesAging in substance, but the description never names or contrasts those 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?
'Use this for any question about what a customer owes' gives a usage context but is over-broad: it plausibly overlaps with getReceivablesAging, which also concerns outstanding balances, and no exclusion or alternative routing is offered. Usage is implied rather than delimited.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
getPaymentHistoryPayments received from a customerARead-onlyIdempotent
Returns payments received from one customer since a given date, with the total. Use this for questions about whether or when a customer has paid.
| Name | Required | Description | Default |
|---|---|---|---|
| customer | Yes | Customer name or numeric ID. | |
| sinceDate | Yes | Only include payments received on or after this date. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true and openWorldHint=false, so the safety profile is covered. The description adds that a total is included in the response, which is genuinely new information. However, it says nothing about limits, ordering, pagination, or what happens when a customer has no payments, leaving real behavioral gaps for a query tool.
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 sentences, zero filler, with the return scope front-loaded before the usage guidance. Every clause carries information an agent needs.
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?
This is a simple two-parameter read tool with full schema coverage and clear safety annotations, so most of the burden is already carried elsewhere. The description plus the mention of a returned total is close to sufficient, missing only edge-case behavior for empty results.
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 both parameters are already documented ('Customer name or numeric ID', 'received on or after this date'). The description's phrasing mirrors the schema rather than extending it, so the 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?
The description gives a specific verb ('Returns') and resource ('payments received from one customer') plus a temporal scope ('since a given date'). It implicitly distinguishes itself from getOverdueInvoices and getReceivablesAging by focusing on payments actually received rather than invoices owed, but it never names those siblings explicitly.
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?
'Use this for questions about whether or when a customer has paid' gives a clear positive usage condition tied to a real agent intent. It stops short of stating when NOT to use it or pointing to the sibling tools (getReceivablesAging, getCustomerSummary) for adjacent questions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
getReceivablesAgingReceivables aging across all customersARead-onlyIdempotent
Returns the aging profile of all outstanding receivables as of a date, bucketed by days overdue. Use this for portfolio-level questions rather than single-customer ones.
| Name | Required | Description | Default |
|---|---|---|---|
| asOfDate | Yes | Evaluate aging as of this date. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and closed-world, so safety and repeatability are covered. The description adds genuinely useful behavior — results are bucketed by days overdue and evaluated as of a supplied date — but never defines the bucket boundaries or says whether zero-balance or credit-balance customers appear, which is what an agent would actually need to interpret the output.
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, no filler, with the core behavior stated first and the usage discriminator second. Nothing could be cut without losing 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?
For a one-parameter read-only tool with full schema coverage and annotations carrying the safety profile, the description covers purpose, scope, and result shape. The only real gap is bucket definitions, which matters for interpreting output that has no output schema to explain it.
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?
Single parameter with 100% schema description coverage, so the schema already explains asOfDate including its date format pattern. The description only echoes that with "as of a date" and adds no format, timezone, or default semantics. Baseline 3 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?
States a specific verb (returns), a specific resource (aging profile of all outstanding receivables), and a specific transformation (bucketed by days overdue). The scope word "all" plus the exclusion of single-customer use makes it immediately distinguishable from getCustomerSummary and getPaymentHistory.
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 second sentence gives an explicit selection rule: portfolio-level questions, not single-customer ones. That is a real when-to-use plus a when-not. It stops short of naming the sibling tool that handles the single-customer case, so the agent must infer the alternative rather than being routed to it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
verifyProvenanceChainVerify the provenance chainARead-onlyIdempotent
Re-derives every hash in the provenance log and reports whether the trail has been modified since it was written. Returns the first inconsistency found, if any.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish that this is a read-only, idempotent, closed-world operation. The description adds useful behavioral detail beyond the annotations: it re-derives every hash and returns the first inconsistency found, rather than merely checking a stored flag.
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 tightly written sentences with no wasted words. The core mechanism is front-loaded, followed by the return behavior.
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 no input parameters, no output schema, and annotations covering safety, the description gives enough context to understand what invoking the tool does. It could be slightly more explicit about the exact return shape for both clean and inconsistent chains, but the essential behavior is covered.
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?
The tool takes zero parameters, so there are no parameter semantics to document. The baseline for a zero-parameter tool is 4, and no additional parameter explanation is needed.
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?
The description states a specific action and object: re-deriving hashes in the provenance log and reporting whether the trail was modified. It is precise enough for an agent to distinguish this integrity-verification tool from the unrelated sibling retrieval tools.
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 clearly implies the tool should be used to check whether a provenance trail has been tampered with. It does not explicitly state when not to use it or name alternatives, but no sibling performs a comparable verification function.
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.
6 tool updates
v0.1.2- First observed
getCustomerSummary - First observed
getOpenTickets - First observed
getOverdueInvoices - First observed
getPaymentHistory - First observed
getReceivablesAging - First observed
verifyProvenanceChain
TDQS
Scored across 6 tools
The tools mostly target clearly distinct operations: customer summary, overdue invoices, payment history, open tickets, aging, and provenance verification. There is some conceptual overlap between getOverdueInvoices and getCustomerSummary because both can answer balance-related questions, but the descriptions distinguish detailed invoice-level debt from a general customer overview.
Five of the six tools follow a consistent getNoun phrase pattern in camelCase, such as getOverdueInvoices and getCustomerSummary. verifyProvenanceChain is a minor verb-style deviation, but it remains clear and readable.
Six tools is a well-scoped set for a focused accounts-receivable and customer-support query server. Each tool appears to earn its place without unnecessary surface area.
The surface covers the core read-only workflows for customer financial health, support context, portfolio aging, and provenance. Minor gaps could include customer search/list operations or more detailed invoice/ticket lookups, but the essential questions in this domain are addressable.
Maintenance
Related MCP Connectors
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Text-to-SQL MCP server: read-only queries on PostgreSQL, MySQL and SQL Server with your real schema
Hosted SQL Server MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceCryptographic proof of every AI decision. An immutable, verifiable audit trail MCP server.1MIT
- FlicenseNot gradedqualityCmaintenanceModel Context Protocol (MCP) server that gives AI assistants a safe, correct data-analyst capability over business metrics - without raw SQL improvisation.-
- AlicenseNot gradedqualityDmaintenanceConfig-driven MCP server that gives AI scoped, auditable database access without exposing the entire database.3 npm6MIT

RecoSearchofficial
AlicenseNot gradedqualityCmaintenanceA deterministic MCP server that governs read-only queries across multiple data sources, returning answers with full provenance (every row cited) or a typed refusal, ensuring LLM answers are traceable and contract-enforced.1Apache 2.0