invoices-mcp
Optional read-only integration that fetches new invoice attachments from Gmail into the local invoice folder via sync_gmail_invoices, allowing emailed CSV/PDF invoices to be parsed and analyzed locally.
Optional read-only integration that copies new invoice files from a configured Google Drive folder into the local invoice folder via sync_drive_invoices, allowing Drive-stored CSV/PDF invoices to be parsed and analyzed locally.
Enables parsing and querying Nubank CSV invoice and account-statement files, including card invoices, transactions, categories, installments, and card-bill payments. Supports tools for invoice listing, spending summaries, transaction search, monthly totals, month comparison, and recurring charge detection.
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., "@invoices-mcpwhat did I spend on food in September?"
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.
invoices-mcp
A small MCP server that lets an AI agent answer questions about your own bank invoices — "what did I spend on food in September?", "which subscriptions am I paying for?", "what changed between August and September?" — without your financial data going anywhere you didn't choose.
You drop invoice files in a folder. The server works out which bank each file is from, reads it with that bank's parser, and gives the agent a handful of narrow, read-only tools.
Supported banks
Bank | Files | What is read |
Nubank | CSV | Card invoice ( |
Itaú | Credit-card statement (Fatura): purchases, installments, cancellations, international purchases (with conversion rate), IOF and payments |
More banks are planned but not built yet. A file from any other bank is not guessed at: it is reported as skipped, with the list of supported banks, by list_invoices. See Adding a bank.
Free. No service to pay for, no account of ours, no API key.
Private by default. It reads files from one folder you choose. In its default mode it makes no network requests and never writes to disk. This is enforced by tests, not just promised (see Privacy).
Checks its own work. Where a statement prints its own total, the server compares it with the sum of the lines it parsed and says so (
check.matchesStatement), instead of silently trusting a regular expression.Optional Gmail and Google Drive. If invoices reach you by e-mail, or live in a Drive folder, the server can fetch them for you — read-only, off unless you turn it on. See Gmail and Drive.
Status: v0.4. Nubank is verified on a real mailbox. Itaú is verified on three real statements (every one adds up to its printed total) plus synthetic ones, including the PDF text-extraction path; run
list_invoiceson your own statement and look atcheckto confirm it adds up. If your files look different, see what to do.
Related MCP server: MCP Gastos
How it works
your files ─────────────▶ a folder ◀──read-only── invoices-mcp ◀──MCP (stdio)── your AI agent
(Nubank CSV, Itaú PDF) ▲ │
└── optional: new files ┴── Gmail / one Drive folder (read-only)For each file the server extracts its text (PDFs are read in-process, no external program), asks each bank parser "is this yours?", and parses it with the one that says yes. The agent never gets a file path and never gets raw access to the files: it calls narrow tools and receives JSON.
Tools
Tool | What it answers |
| Which banks and formats are supported |
| Files found: bank, month, due date, transaction count, totals, and whether the parsed lines match the statement's own total. Unreadable files are listed under |
| Totals, spend by bank and category, top merchants, largest purchases (filter by bank, invoice, dates, category) |
| Find transactions by text, bank, date range, category or amount (newest first, max 200 rows) |
| Spend, refunds and payments per month, optionally for one bank or category |
| What changed between two months, by category, biggest change first |
| Likely subscriptions: same merchant, similar amount (±5%), about once a month |
| Only when Gmail is configured. Download new invoice attachments into the folder |
| Only when a Drive folder is configured. Copy new invoice files from that one folder into the local folder |
Every analysis tool takes an optional bank filter (nubank, itau).
What "spend" means
All amounts are in BRL. Amounts are handled as integer cents, so totals never pick up floating-point noise.
Term | Meaning |
| Purchases and fees (money out), before refunds |
| Cancellations and refunds (money back) |
| Card-bill settlements: the payment shown on a card invoice, and the matching "pay the bill" outflow on an account statement. Never counted as spending, so paying a card from your account is not double-counted |
|
|
Categories and merchants in summaries are net of refunds. Itaú's own category labels are normalised (HEALTH → saúde, SUPERMARKET → supermercado); international purchases are international, and the IOF charged on them is fees.
Quick start
Requires Node.js 20 or newer.
git clone <this repo>
cd invoices-mcp
npm install
npm run buildPut your invoice files in a folder, for example
C:\Users\you\Documents\invoices— Nubank CSVs and Itaú PDFs can sit side by side.Try it on the included fake data first (one Itaú PDF and four Nubank CSVs):
node dist/index.js --dir sampleConnect it to an agent
Claude Code — either run
claude mcp add invoices -- node /path/to/invoices-mcp/dist/index.js --dir /path/to/invoicesor put this in a project's .mcp.json. Claude Desktop uses the same block in claude_desktop_config.json:
{
"mcpServers": {
"invoices": {
"command": "node",
"args": ["C:/path/to/invoices-mcp/dist/index.js", "--dir", "C:/Users/you/Documents/invoices"]
}
}
}You can also set the folder with the INVOICES_DIR environment variable instead of --dir.
Things to ask
"List my invoices and tell me if any of them don't add up."
"Summarise my spending for September. What are my top three categories?"
"Which subscriptions am I paying for, and what do they cost per year?"
"Compare August and September on the Itaú card. What went up the most?"
"Find every purchase over R$ 150 this quarter."
Supported formats in detail
Nubank (CSV)
The layout is detected from the header row; comma or semicolon delimiters both work, as do 1.234,56 and 1,234.56 style amounts and ISO or dd/mm/yyyy dates.
Layout | Header | Notes |
Card invoice |
| Purchases are positive; the payment received is negative and tagged |
Account statement |
| Negative |
Itaú (PDF)
The PDF's text is read and flattened (so differences in line breaks between PDF readers don't matter), then:
the due date (
Vencimento) fixes the year of every row, since rows only printDD/MM; a December purchase on a January statement becomes last year;purchases are read as date, merchant, optional installment
NN/NN, amount, with the category and city printed underneath each row;cancellations (
CANCELAMENTO DE COMPRA) are negative and net against the purchase;international purchases are read with their original amount and currency; the R$ figure is identified by checking which printed amount equals another times the printed Dólar de Conversão rate;
the IOF passed on for international purchases is a
feesline, because it is part of the invoice total;the "Compras parceladas – próximas faturas" section (future installments) is ignored: it is not charged on this invoice;
repeated page headers,
continua...markers and stray page numbers are discarded;card numbers, cardholder names and addresses are never copied into the result.
Self-check. The parsed lines are compared with the statement's card subtotal, its international total and its Total dos lançamentos atuais. Any difference becomes a warning, and list_invoices shows check: { statementTotalBRL, parsedTotalBRL, matchesStatement }. If it says false, a line was missed or misread — don't trust that invoice's numbers until it's looked at.
Damaged text layers. Some statements come out of the PDF with stray spaces inside words and numbers (6.634 ,53, 128,5 0, 04/ 08) and with the two columns interleaved. The parser repairs number-shaped damage and matches section headings loosely, but merchant names stay as printed (REST AUR ANT E), categories are not read for such statements (they are uncategorized), and the result carries a warning saying so. It is accepted only because the lines still add up to the printed total; a statement it cannot read, or whose total it cannot find, is rejected rather than guessed at.
Password-protected PDFs. If your statements need a password, set INVOICES_PDF_PASSWORD. It is used only to open the file and is never stored or logged.
If a file does not parse
list_invoices reports skipped files with a reason, and check flags statements whose lines don't add up. If your file uses a different layout, open an issue describing it without including transactions or personal data (for a CSV, only the header line; for a PDF, which section looks different).
Optional: fetch invoices from Gmail or Google Drive
Both are off by default, share one sign-in, and can be used together. Use Gmail if invoices reach you by e-mail (an exported CSV you forward to yourself, or a PDF statement a bank sends). Use Drive if you keep invoice files in a Drive folder. Each adds one tool — sync_gmail_invoices, sync_drive_invoices — that copies new CSV and PDF files into the invoice folder; everything after that, parsing and analysis, is the same local code.
One-time setup (about 10 minutes)
In Google Cloud Console create a project and enable the Gmail API and/or the Google Drive API (whichever you will use).
Configure the OAuth consent screen (External) and add your own Google account as a test user (without this Google answers
403 access_denied).Create credentials → OAuth client ID → application type Desktop app, and download the JSON. Keep it somewhere outside this project.
Authorise once. This prints a URL; open it and approve read-only access. Say which sources you want:
node dist/index.js auth --google-credentials /path/to/client.json --scopes gmail,drive(
--scopes gmail,--scopes driveor--scopes gmail,drive; the default isgmail.) To add Drive later, run it again with the scopes you want.Tell the server what to read, in your MCP configuration:
"args": ["C:/path/to/invoices-mcp/dist/index.js", "--dir", "C:/Users/you/Documents/invoices", "--google-credentials", "C:/path/to/client.json", "--gmail-query", "label:invoices", "--drive-folder", "https://drive.google.com/drive/folders/<folder id>"]Use either option or both. The environment variables are
INVOICES_GOOGLE_CREDENTIALS,INVOICES_GMAIL_QUERYandINVOICES_DRIVE_FOLDER. Then ask your agent: "Fetch new invoices from Drive and Gmail, then summarise this month."
How the access is kept narrow
The source is yours, not the agent's. The Gmail search and the Drive folder are fixed when the server starts. The Gmail query must contain
from:,label:orsubject:; the Drive folder must be a plain folder id or folder link (anything that could alter the Drive search is refused at startup). The tools' only parameter is a count (how many recent messages or files to look at). A prompt-injected agent cannot widen the search or point the server at another folder.Drive: one folder, no subfolders. Only files listed directly in that folder are considered, and each file must report that folder as its parent. Folders, Google Docs and Google Sheets are not downloaded.
CSV and PDF only. Gmail always appends
has:attachment (filename:csv OR filename:pdf); Drive considers only.csv/.pdffiles. Images, documents and everything else are never downloaded.Only real bank invoices are kept. Every file must be recognised and read by one of the bank parsers; anything else is discarded without being saved.
Gmail: email text is never read. Only the attachment structure is requested, not message bodies.
One safe-save routine for every source (
src/google/save.ts): files are renamed from safe characters only (gmail-<id>-<name>,drive-<id>-<name>), so../../x.csvcannot leave the folder; an existing file is never overwritten or downloaded again; files are capped at 5 MB and 50 per run.Read-only scopes (
gmail.readonly,drive.readonly). The Gmail and Drive clients send nothing butGETrequests, so nothing in your mailbox or your Drive can be changed, moved, shared or trashed; tests fail if that ever stops being true.Network limited to Google. Sign-in and API calls go only to
accounts.google.com,oauth2.googleapis.com,gmail.googleapis.comandwww.googleapis.com/drive/v3/files(plus a loopback address for the sign-in redirect). A test fails if any other host appears.Tokens. Only a refresh token is stored, at
~/.invoices-mcp/google-token.json(override withINVOICES_GOOGLE_TOKEN). Access tokens live in memory. The OAuth flow uses PKCE and checksstate, and the credentials file is rejected if its token endpoint isn't Google's. Before each sync the server checks that the saved token actually includes the needed scope and says how to add it if not.
Honest limits of the Google options
The permissions are broader than what the code does. Google offers no narrower scope that can read Gmail attachments or the contents of an arbitrary Drive folder. So the token can technically read your whole mailbox (
gmail.readonly) and every file in your Drive (drive.readonly). This server only runs the search and folder you configured, but the credential itself is powerful. Consider a dedicated Google account for invoices, keep the token file private, and know how to revoke access: https://myaccount.google.com/permissions.Tokens expire weekly in "Testing" mode. While your OAuth app is unverified, Google expires refresh tokens after 7 days; re-run
authwhen the tool says so.Drive: subfolders are not read; a file you later edit in Drive is not downloaded again (a new file name or a deleted local copy is); Google Sheets are not read (export one as CSV first).
Downloaded files are real financial data sitting in your folder. Protect it like any bank statement.
Some banks protect PDF statements with a password; set
INVOICES_PDF_PASSWORDso they can be validated and saved.
Privacy
Two modes, stated plainly:
Default (no Gmail options)
Reads only
*.csvand*.pdffiles directly inside the configured folder (no recursion). A file whose real path leaves the folder, such as a symlink, is skipped.Read-only. No code path writes, renames or deletes files.
No network access and no child processes. PDFs are read in-process. The transport is stdio only. The Gmail code is not even loaded.
Nothing is logged: transaction contents are never written to stdout or stderr (stdout carries the MCP protocol).
File size is capped at 10 MB and file count at 500.
With Gmail and/or Drive enabled
Everything above still holds for the parsing and analysis code. The only code allowed to use the network or write files lives in
src/google/, and only thesync_gmail_invoicesandsync_drive_invoicestools reach it.
test/privacy.test.ts turns this into a build failure: outside src/google/ no source may import a network or process module, call fetch, or write a file; the Google modules may only mention Google hosts, may only request the two read-only scopes, may not reference mutating endpoints, and the Gmail and Drive clients may send nothing but GET requests; nothing may log through console; and no status line may interpolate a token or transaction contents. .gitignore excludes every *.csv and *.pdf except the fake files in sample/, so you can't commit your real data by accident.
One more honest limit: the agent you connect decides what happens to the answers. If the host sends tool results to a cloud model, those results (for example, a list of your transactions) go to that model's provider. This server keeps the files local; it can't control what a connected model does with what it is shown. For fully local use, connect it to a local model.
Adding a bank
The design keeps this small. A bank is one file that answers two questions about a file's text — is this yours? and what are the transactions? — plus one line in a list.
Add the bank's id to
BankIdinsrc/types.ts.Write
src/banks/<bank>.tsexporting aBankParser(detectandparse, both plain functions of the file's text; throwBankFormatErrorwhen a recognised file can't be read). Return amounts as integer cents, positive for money out, negative for money back, and tag card-bill settlementspayment.Add it to
PARSERSinsrc/banks/index.ts.Add a synthetic fixture and tests. Never commit a real statement.
Nothing else changes: the store, every tool, the Gmail sync and the privacy tests all go through the same ingest() path. If the bank prints totals, reconcile against them as the Itaú parser does.
Development
npm test # parsers, analytics, file loading, Gmail (against fakes), privacy rules, and end-to-end MCP sessions
npm run typecheck
npm run dev -- --dir sampleThe end-to-end test starts the real server over stdio and talks to it with the official MCP client, the same way an agent host would. PDF tests build real PDF files from fake statement text, so the extraction path is exercised too. One test (symlink escape) is skipped on systems that don't allow creating symlinks, such as Windows without developer mode.
src/types.ts shared types: Transaction, BankParser, ParseResult
src/parse.ts bank-neutral helpers: CSV, dates, money (integer cents)
src/banks/ one module per bank + the registry (nubank.ts, itau.ts, index.ts)
src/pdf.ts in-process PDF text extraction
src/ingest.ts bytes -> text -> the parser that recognises it
src/store.ts read-only loader: top-level .csv/.pdf only, path-escape guard, mtime cache
src/analytics.ts summaries, month comparison, recurring-charge detection (pure functions)
src/server.ts MCP tool definitions
src/index.ts stdio entry point and CLI (including `auth`)
src/google/ optional Gmail and Drive: shared OAuth (auth.ts), REST clients and syncs
(gmail-api.ts, gmail-sync.ts, drive-api.ts, drive-sync.ts), one safe-save routine (save.ts)
sample/ synthetic invoices (not real data)Known limitations
Recurring-charge detection is heuristic: one charge a month within ±5%. It misses subscriptions whose price changed and may flag a habit that happens to be regular.
Categories are whatever the bank printed; Nubank's e-mailed card CSV has none, so those rows are
uncategorized.Only BRL totals; foreign-currency purchases are kept in BRL with the original amount shown.
Emailed Nubank statements are named after the invoice due date, so an invoice holds the previous month's purchases. Invoice
monthfollows the due date;monthly_totalsandcompare_monthsgroup by transaction date, so the two views can differ.Itaú statements with several cards (additional cardholders) are read as one invoice; per-card breakdown is not exposed.
Built for personal use on a handful of files, not as a financial-reporting tool.
Built with AI assistance
Most of the code, tests and documentation in this repository were written by an AI assistant (Claude Code, by Anthropic), working from my requirements and constraints: read-only access, financial data stays local, the agent cannot change which mailbox query or Drive folder is read, and every parse must reconcile with the totals printed on the statement. I directed the design, supplied the real statements and accounts it was tested against, and ran it. Nubank and Itaú parsing was checked against my own real files (every Itaú statement adds up to its printed total), but that is a handful of files from one person, so treat it as unreviewed software: read the code, and check list_invoices's check field against your own statements before relying on any number.
License
MIT
Available Tools
7 toolscompare_monthsCompare two monthsARead-onlyIdempotent
Compare spending by category between two calendar months and show what changed most. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| bank | No | Only this bank: nubank, itau. Omit to include every bank. | |
| month_a | Yes | Earlier month, YYYY-MM | |
| month_b | Yes | Later month, YYYY-MM |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnly, idempotent, non-destructive, closed-world behavior, so the safety profile is covered. The description usefully goes beyond them by disclosing the currency (BRL) and the sign convention — positive = money out, while refunds, card payments, and deposits are reported separately as credits — which is non-obvious domain context an agent needs to interpret results.
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 tight sentences: the first front-loads what the tool does, the second adds the interpretation rules for amounts. Nothing is redundant and every clause carries information an agent would otherwise have to guess.
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 and a read-only annotation set, the description compensates well by explaining currency and sign semantics. The remaining gap is that it never sketches the return shape (ranked deltas? per-category breakdown?), which is the main thing an agent must infer.
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%: month_a/month_b are documented as earlier/later YYYY-MM and bank as an optional nubank/itau filter, so the schema does the heavy lifting. The description adds no parameter-level detail (format, ordering constraints, or default behavior when bank is omitted) beyond what the schema already states, matching the baseline 3.
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 names a specific verb (compare), resource (spending by category), and scope (two calendar months, highlighting what changed most), so the function is unambiguous. It does not explicitly name or contrast itself with siblings like monthly_totals or spending_summary, so it lands just short of the top mark.
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?
Usage is implied by the comparison framing — an agent can infer it applies when two months must be compared — but there is no explicit when-to-use/when-not-to-use statement and no mention of when to prefer spending_summary or monthly_totals instead. Prerequisites (e.g., whether month_b must follow month_a) are only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_recurring_chargesFind recurring chargesARead-onlyIdempotent
Detect likely subscriptions: the same merchant charged a similar amount (within 5%), about once a month, in several different months. Installment purchases are ignored. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| bank | No | Only this bank: nubank, itau. Omit to include every bank. | |
| min_months | No | Months a charge must appear in (default 3) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed world), so the description's added value is the amount semantics: BRL currency, positive = money leaving the account, and refunds/card payments/deposits reported separately as credits. That sign convention is genuinely non-obvious context. It stops short of describing result shape or how partial matches are handled.
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 dense sentences, front-loaded with the detection rule before the caveat and the sign/currency note. Every clause carries information an agent cannot get from the schema or annotations.
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 the heavy lifting on what counts as a match and how amounts are signed, which is the essential context. It omits what a returned result contains and whether near-misses are surfaced, but nothing critical to invoking the tool correctly is missing.
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 (bank, min_months) are already documented and the baseline is 3. The description reinforces the cadence semantics ('about once a month', 'in several different months') that contextualize min_months and adds the 5% amount tolerance, but adds no syntax or format detail beyond the schema.
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 ('detect likely subscriptions') and then defines the exact heuristic: same merchant, amount within 5%, roughly monthly, across several months. That precision clearly separates it from siblings like spending_summary or search_transactions, which do not infer recurrence.
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 when the tool is appropriate (identifying subscriptions) and gives one exclusion (installment purchases are ignored), but never names an alternative sibling or states when to prefer raw transaction search instead. Usage must be inferred from the purpose.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_banksList supported banksARead-onlyIdempotent
Which banks and file formats this server can read. Files from other banks are not supported yet and are reported as skipped by list_invoices.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, closed-world, so the safety profile is covered. The description adds genuinely new behavioral context: unsupported banks are not errored but silently reported as skipped via list_invoices, which tells the agent what happens to bad input — useful beyond the structured fields.
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, front-loaded with what is returned and followed by the one caveat that matters. No filler, nothing that could be cut without losing meaning.
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?
No output schema exists, so the description carries the return-value burden, and 'which banks and file formats' adequately conveys the shape. It is complete enough for such a simple, annotation-protected read, with only minor room to describe the format of the listing.
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 per the rubric the baseline is 4. The description does not need to explain inputs, and it correctly frames the call as argument-free discovery.
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 the resource precisely — the banks and file formats the server can read — and the name/title reinforce it. It does not use an explicit verb, but it distinguishes itself from siblings by referencing list_invoices' skip behavior, so an agent can tell it apart from the transaction-oriented 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?
Usage is only implied: you call it to discover what input is acceptable, and the note that unsupported files are 'skipped by list_invoices' hints at the downstream consequence. There is no explicit 'use this before X' or when-not-to-use guidance, so the agent must infer the trigger.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_invoicesList invoicesARead-onlyIdempotent
List the invoice files that were found: bank, month, type (card or account), due date, transaction count and spend. Where a statement prints its own total, check says whether the parsed lines add up to it. Files that could not be read are listed under skippedFiles. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| bank | No | Only this bank: nubank, itau. Omit to include every bank. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this is a safe, idempotent, read-only, closed-world operation, yet the description adds substantial non-obvious behavior: the `check` reconciliation flag, the skippedFiles bucket for unreadable files, BRL currency, and the sign convention that positive amounts are debits while refunds/payments/deposits are credits. These semantics are exactly what an agent needs to interpret results and cannot be inferred from the 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?
Three dense sentences, front-loaded with the core purpose, with no filler or redundancy. It is information-rich rather than bloated, though the sign-convention sentence is long and could be split for scannability.
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 correctly takes on responsibility for return-value semantics (fields, currency, credits vs debits, skippedFiles) and does so well. It omits the optional bank filter and any notion of ordering, pagination, or result size, which leaves a small gap for a listing tool.
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 `bank` enum parameter is fully documented by the schema itself. The description mentions 'bank' only as a returned attribute and never explains the filtering behavior, so it adds nothing over the schema. Baseline 3 is appropriate when the schema carries the load.
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 clear verb+resource ('List the invoice files') and enumerates exactly what each entry contains (bank, month, type, due date, transaction count, spend), which is specific enough to separate it from siblings like spending_summary or monthly_totals. It stops short of naming an alternative, so it doesn't quite reach the 5 level.
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?
There is no explicit when-to-use or when-not-to-use guidance. Nothing tells the agent why it would call list_invoices rather than search_transactions or monthly_totals, and no preconditions or exclusions are stated. Usage is only inferable from the returned fields.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
monthly_totalsMonthly totalsARead-onlyIdempotent
Spend and credits per calendar month, optionally for one bank or category. Months come from transaction dates. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| bank | No | Only this bank: nubank, itau. Omit to include every bank. | |
| category | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent and non-destructive, so the safety profile is covered. The description adds substantive semantics beyond the annotations: months are derived from transaction dates, amounts are in BRL, and a positive amount means money leaving the account while refunds/payments/deposits appear separately as credits. That sign convention is a real behavioral detail an agent could not otherwise infer.
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?
Four short sentences, each carrying distinct information; the core output semantics (what is counted and how it is signed) are front-loaded before the secondary currency and credit notes.
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 carries the burden of explaining returns, and it does: it defines the two reported quantities, the currency, the sign convention, and where credits land. For a two-optional-param aggregation tool, nothing essential is missing.
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 50%: bank is fully documented with its enum in the schema, while category has no description in either place. The description clarifies that these are optional scoping filters ('optionally for one bank or category'), but does not explain how category matches or how multiple values combine, so it only partially compensates for the gap.
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 names the resource (spend and credits) and its grouping (per calendar month), which is specific enough to distinguish it from a raw listing. It does not explicitly contrast with the closest siblings such as spending_summary or compare_months, so an agent must infer the boundary.
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?
It states that bank and category are optional narrowing filters, implying usage, but never says when to choose this over spending_summary or compare_months, nor when it would be inappropriate. Usage is implied rather than guided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_transactionsSearch transactionsARead-onlyIdempotent
Find individual transactions by text, bank, date range, category or amount, newest first. min_amount and max_amount compare the absolute amount in BRL. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | ||
| bank | No | Only this bank: nubank, itau. Omit to include every bank. | |
| from | No | ||
| limit | No | Maximum rows to return (default 50, max 200) | |
| query | No | Text to look for in the description (case and accent insensitive) | |
| invoice | No | ||
| category | No | ||
| max_amount | No | ||
| min_amount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so safety is covered structurally. The description adds real behavioral context beyond that: results are ordered newest first, amounts are normalized to absolute BRL, and the sign convention is documented along with the fact that refunds/card payments/deposits are reported separately as credits. It does not mention the default row cap or the absence of pagination.
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 tight sentences with zero filler; the searchable dimensions and ordering come first, followed by the amount semantics that most need emphasis. Every sentence adds information an agent would otherwise have to guess.
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 read-only, no-required-parameter search tool with no output schema, the description covers the essentials: what can be filtered, ordering, currency, and sign semantics. The remaining holes are the unexplained `invoice` parameter and no statement about row limits relative to the 200 max, which matter because limit is the only way to bound a broad query.
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 only 33%, so the description must carry weight, and it does for the highest-risk parameters: min_amount/max_amount compare the absolute value, and the sign of a returned amount carries meaning (positive = money out). This prevents systematic misinterpretation of spend vs. credit. Gaps remain: the `invoice` parameter is undocumented in both schema and description, and the from/to date format is never stated.
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 ('Find individual transactions') plus the full set of filter dimensions and the result ordering. The word 'individual' implicitly contrasts with the aggregate siblings (spending_summary, monthly_totals, compare_months, find_recurring_charges), so an agent can pick the row-level tool without inspecting a 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?
Usage is implied by listing the filterable dimensions, so an agent knows this is the search/filter entry point. However, it names no alternative and gives no when-not condition — e.g. it never says to prefer spending_summary or monthly_totals for totals rather than enumerating rows.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spending_summarySpending summaryARead-onlyIdempotent
Totals, spend by bank and category, top merchants and largest purchases. Optionally narrow by bank, invoice, date range or category. Amounts are in BRL. A positive amount is money leaving the account or card (a purchase or fee); refunds, card payments and deposits are reported separately as credits.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | End date, inclusive | |
| bank | No | Only this bank: nubank, itau. Omit to include every bank. | |
| from | No | Start date, inclusive | |
| invoice | No | Invoice id from list_invoices, e.g. Nubank_2026-09-04 | |
| category | No | Category name as shown in the export |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent and non-destructive, so the safety profile is covered. The description adds genuinely non-obvious semantics: amounts are in BRL, positive values represent outflows, and refunds/card payments/deposits are reported separately as credits — critical for interpreting results correctly.
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, tightly front-loaded with capability first and the sign/currency convention last. Every sentence carries information, though the opening noun-list fragment is dense without a lead verb.
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 usefully enumerates the returned aggregations, and the sign convention covers the biggest interpretation risk. It omits how many merchants/purchases are returned and what the default date range is when from/to are omitted, both of which remain unknown to the agent.
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 the filters and their formats (date pattern, bank enum, invoice id example) are already documented in the schema. The description restates the filter set but adds no syntax or default-value detail beyond it, making 3 the correct baseline.
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 names specific outputs (totals, spend by bank and category, top merchants, largest purchases), which is a clear verb+resource statement an agent can act on. It does not, however, differentiate itself from siblings like monthly_totals or compare_months, which cover overlapping analytic ground.
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?
It states the available narrowing dimensions ('Optionally narrow by bank, invoice, date range or category'), which implies when the filters apply, but never says when to choose this tool over monthly_totals, compare_months or search_transactions. Usage is inferable but not routed.
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.
7 tool updates
v0.4.0- First observed
compare_months - First observed
find_recurring_charges - First observed
list_banks - First observed
list_invoices - First observed
monthly_totals - First observed
search_transactions - First observed
spending_summary
TDQS
Scored across 7 tools
Most tools have clearly distinct purposes: list_banks vs list_invoices separate source banks from statement files, search_transactions is item-level, compare_months and find_recurring_charges are specific analyses. spending_summary and monthly_totals overlap somewhat since both produce aggregated totals, but the monthly and category/bank breakdowns help differentiate them.
All names use consistent snake_case, which is good. However, the verb-first convention (list_, search_, compare_, find_) is broken by two noun-phrase names (spending_summary, monthly_totals), a minor deviation that is still readable.
Seven tools sit squarely in the well-scoped range for an invoice-analysis server. Each tool covers a distinct analytic need (inventory, totals, trends, search, comparison, recurring detection) with no redundant entries.
The read-only analysis surface is well covered: discovery, summaries, monthly trends, search, comparison, and recurring-charge detection. Minor gaps remain, such as per-transaction drill-down detail or category management, but these are workable given the apparent read-only scope.
Maintenance
Related MCP Connectors
Connect your Nubank account to AI via Brazil's Open Finance: balances, statements, cards, investment
Turn bank statement PDFs, CSVs, XLSX and OFX into categorised transactions plus a summary.
Turn bank statement PDFs into categorized, balance-checked transactions and reports.
Ask Claude about your income and spending, and import bank statements. No bank login.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables AI agents to manage personal expenses through natural language conversations. Supports adding, searching, and analyzing transactions with automatic categorization and financial insights.3MIT
- FlicenseNot gradedqualityDmaintenanceEnables personal expense management through CSV storage, allowing AI agents to add, query, and analyze expenses with automatic categorization and payment method validation.-
- AlicenseNot gradedqualityCmaintenanceConnects Brazilian banks (Itaú, Bradesco, Nubank, etc.) to AI agents, enabling natural language queries about expenses, statements, investments, and credit cards via regulated Open Finance.25MIT
- AlicenseNot gradedqualityBmaintenanceEnables querying your own local bank and credit card statement files (CSV or PDF) in plain language, so you can discover forgotten recurring subscriptions, compare spending across months and quarters, and look up transactions without any bank logins or network calls.MIT