# Cashflow MCP server > Cashflow keeps track of a household's day-to-day money: what came in, what went out, and what's coming up, and keeps the statements, receipts and tax forms behind it. Connect any MCP agent to it once and it can answer money questions and help keep the books tidy. Server: https://cashflow.tech/api/mcp Works with Claude, ChatGPT, Gemini, Muse, OpenClaw, Claude Code, Codex, Cursor, VS Code, and any MCP app. Setup steps for each: https://cashflow.tech/connect (plain text: https://cashflow.tech/connect.md) ## Connect in one step Paste this into your agent: ``` Connect to Cashflow, the app that keeps track of my day-to-day money, as an MCP server at https://cashflow.tech/api/mcp (Streamable HTTP, OAuth 2.1 with PKCE; client ID metadata documents or dynamic client registration; the first call without a token answers 401 with the discovery address). If Cashflow's tools are already available to you, skip to "Once connected". If you have a built-in way to add a remote MCP server with OAuth, use it rather than writing a client of your own (Claude Code: claude mcp add --transport http --scope user cashflow https://cashflow.tech/api/mcp; Codex: codex mcp add cashflow --url https://cashflow.tech/api/mcp, then codex mcp login cashflow). If you can't add one yourself (most chat apps, like Claude, ChatGPT and Gemini, only add them from their settings), don't stop there: tell me how to add it in your app's settings (the steps for each app are at https://cashflow.tech/connect), and continue once I've added it. Before you open the sign-in link, tell me what will happen: I sign in with Google and approve the connection in my browser. Then open the link. Read https://cashflow.tech/docs/llms.txt for how the tools work. Once connected: 1) call query with { digest: true } and tell me in a few lines what needs my attention today and what you can do for me from now on; 2) if no bank account is connected yet, call the accounts tool so I can connect one; 3) ask whether you should use Cashflow whenever I ask about my money, and if I say yes, save this in your memory or instructions: Answer questions about my spending, bills and balances from Cashflow rather than guessing, and ask me before changing anything in it. ``` ## Authentication The server speaks the Model Context Protocol over Streamable HTTP. Sign-in is OAuth 2.1 with PKCE, and a client identifies itself with a client ID metadata document or dynamic client registration: a client that follows the protocol's discovery needs nothing else. Point it at the address, sign in with Google, approve, and the tools are there. No API keys needed. An agent that can't open a browser, like a scheduled job, can skip sign-in: create a personal access token in Settings and send it as a bearer header. There is no separate REST API. MCP is the interface. ## What an agent can do - **Get its bearings in one call.** `query { digest: true }` returns what needs attention today, such as a low balance coming, a missed bill, an unusual charge or a bank connection that stopped working. - **Answer money questions.** Spending summaries, breakdowns by category or merchant, month-over-month trends, income vs. expenses, and a search over every transaction, all from the one `query` tool. - **Look ahead.** Recurring bills and subscriptions with their next dates, what the person is on track to have, and how they are doing against their plan for the year. - **Keep it tidy.** Recategorize, rename a merchant, tag, or ignore a transaction; write a rule so the next one is handled automatically. - **Keep the paper trail.** File statements, receipts and tax forms against the transactions and accounts they back up, and check a statement against the ledger row by row. - **Run on a schedule.** The digest is cheap and read-only, so it is safe every morning. `query { detail: true, since }` returns only the transactions that arrived since the last run, with an `as_of` to pass next time. Cashflow is about cash. There is no net worth, investment performance or credit score here, and the data is one household's own. ## Tools - `query` (read-only): Unified financial query tool with auto-detected modes: spending summaries, grouped breakdowns, trends over time, income vs. expense reports, transaction detail, recurring bill detection, cash flow forecasting, unusual-charge detection, transfers between your accounts, and data health checks. Supports filtering by category, party (the merchant or person on the other side of a transaction), account, tags, date range, and amount. - `annotate` (non-destructive): Classify and organize transactions. Set categories, parties, tags. Mark transfers or ignored transactions. Apply rules in bulk. Detect recurring bills and subscriptions. All changes are reversible — raw bank data is never modified. - `admin` (write): Manage your setup: categories, category groups, parties, tags, accounts, recurring bills, transaction rules, plans and documents, plus bank sync and file import. Supports list, create, update, rename, merge, and delete (activate/deactivate via update). Rules use a specificity-based engine with a preview-before-create workflow. - `accounts` (read-only): An interactive card of your connected banks and accounts, with when each last updated and one-click buttons to sync, reconnect, disconnect, or connect a new bank. Shown in assistants that can display one. - `paycheck_plan` (read-only): An interactive card of your next few paychecks: for each one, the bills due before the next check, what to put aside toward the bigger bills that come less often, and what's left. Built from your detected paycheck and recurring bills. Shown in assistants that can display one; the same plan comes back as data for any agent. ## How the server behaves - Amounts are in the household's currency (dollars for a US household), never cents; a household outside USD gets `ccy` on each response. A transaction's `amt` is signed (money in positive, money out negative); every other total says which way it reads in a `sign` field. - `query` takes filters at the top level; `annotate` takes them inside `filter`. No dates means all time, which for a write means an all-time write. - For cash or statement comparisons, use `view: "as_booked"` with the same accounts, dates and financial-kind scope: it reports recorded activity. `view: "adjusted"` applies reporting entries without changing those transactions and may differ from cash that moved in the range; `reconcile: true` shows the bridge. Period sums cover the exact selected dates, even when calendar column labels are wider. P&L operating net is income minus expenses; change in cash also includes investment, financing and transfer activity. - Writes over a filter are two steps: `preview: true` returns the matches and a token, and the same call with that token makes the change. The token is bound to exactly those transactions, so it goes stale if they change. - Rules work the same way: `admin { entity: "rule", action: "preview" }` shows what a rule would catch before `create` saves it. - Look names up before filtering on them: `admin { entity: "category" | "party" | "tag", action: "list" }`. Category and merchant ("party") filters are exact; `search` is fuzzy. - `query { explain: true, transaction_id }` answers "why is this categorized that way" before anyone changes it. - Responses are terse to save context: short field names (`amt`, `cat`, `grp`, `desc`, `acct`, `dt`) and nothing empty. ## Guides ### Start every conversation oriented 1. `query {"digest":true}` — what needs attention today: a low balance coming, a missed bill, an unusual charge, a broken bank connection Guardrail: Read-only and bounded, so it is safe to run first, every time. ### Fix a wrong category 1. `query {"explain":true,"transaction_id":"00000000-0000-4000-8000-000000000000"}` — why it was categorized that way — the bank, a rule, or a person (the id comes from `detail: true`) 2. `annotate {"action":"categorize","transaction_ids":["00000000-0000-4000-8000-000000000000"],"category_name":"Groceries"}` — fix this one transaction Guardrail: If a rule put it there, change the rule instead, or the next sync puts it back. ### Clean up in bulk 1. `annotate {"action":"categorize","filter":{"party":"Starbucks","period":"this_year"},"category_name":"Coffee","preview":true}` — see what would change, and get a token 2. `annotate {"action":"categorize","filter":{"party":"Starbucks","period":"this_year"},"category_name":"Coffee","token":""}` — make exactly that change Guardrail: A filter with no dates covers all time. Scope it, and check the range the preview echoes. ### Turn a fix into a rule 1. `admin {"entity":"rule","action":"preview","description_pattern":"BLUE BOTTLE","set_category_name":"Coffee"}` — every past transaction the rule would catch, and a token 2. `admin {"entity":"rule","action":"create","description_pattern":"BLUE BOTTLE","set_category_name":"Coffee","token":""}` — save it; it applies to new transactions as they arrive Guardrail: Match on the bank's description, not the merchant name: merchant names can change on a resync. ### Review bills and subscriptions 1. `query {"recurring":true}` — every bill and subscription, with its next date and amount 2. `admin {"entity":"recurring_series","action":"update","id":"","active":false}` — stop tracking one that ended Guardrail: Deactivating stops the forecast counting it; it cancels nothing with the merchant. ### Can I afford this? 1. `query {"forecast":true,"scenario":{"add":[{"name":"New car payment","amount":450,"freq":"monthly"}]}}` — the projected balance with and without the new payment Guardrail: Nothing is saved: a scenario is a what-if on top of the forecast. ### Check the year against the plan 1. `query {"plan_status":true}` — income, spending and big events against the plan, and what's running ahead or behind Guardrail: A real change in plans is a plan revision (with a reason), not a fudged category. ### File a receipt against a charge 1. `admin {"entity":"document","action":"request_upload","filename":"receipt.pdf"}` — an upload address and a ready-to-run command; the file goes there, not through the conversation 2. `admin {"entity":"document","action":"create","upload_id":"","doc_type":"receipt","title":"Plumber invoice","link_transaction_ids":["00000000-0000-4000-8000-000000000000"]}` — file it against the charge it explains 3. `admin {"entity":"document","action":"substantiate","start":"2026-09-01","end":"2026-09-30"}` — which charges in the month still have nothing filed against them Guardrail: Linking is explicit: name the charge. Nothing is matched for you from the amount or the merchant. ### Check a statement against your transactions 1. `admin {"entity":"document","action":"request_upload","filename":"statement.pdf"}` — an upload address for the statement PDF 2. `admin {"entity":"document","action":"create","upload_id":"","doc_type":"statement","title":"Chase card, September","link_account_ids":["00000000-0000-4000-8000-000000000000"]}` — file it to its account (the id from `admin { entity: "account", action: "list" }`); the billing cycle is read off the statement itself 3. `admin {"entity":"document","action":"tie_out","id":""}` — every statement line against the ledger, both ways: lines with no transaction here, and transactions the statement never printed Guardrail: Link the statement to its account, or there is nothing to check it against. It compares lines, not balances. ### Run a morning check on a schedule 1. `query {"digest":true}` — what to flag today 2. `query {"detail":true,"since":"2026-10-07T08:00:00.000Z"}` — only what arrived since the last run; store the `as_of` it returns for next time Guardrail: Both are reads. Ask the person before any annotate or admin call a schedule wants to make. ## Query modes The first mode whose trigger matches wins, in this order. - `digest:true` → daily flag briefing - `health:true` → data-quality dashboard - `explain:true+transaction_id` → why this name/category - `plan_status:true` → annual plan vs actuals - `vested_interest:true` → private holdings register - `recurring:true` → bills & subscriptions - `forecast:true|forecast_months|scenario` → projected balance runway - `paychecks:true` → income streams - `account_summary:true` → per-account rollup - `flows:true` → money between own accounts - `reimbursables:true` → money owed back - `tag_report:true` → per-tag project ledger - `tax_ledger:true` → taxes paid/refunded - `tax_documents:true` → expected tax forms - `clusters:true` → merchant dedup candidates - `detail:true` → individual transactions - `by+granularity` → P&L matrix - `granularity alone` → trend over time - `by alone` → aggregate with pct - `period alone` → income/expenses/net summary ## Admin entities - `category` — global taxonomy, READ-ONLY (list, plus this household's own tags/notes on it). Reads: list. Writes: add_tag, remove_tag, set_note, remove_note. - `category_group` — its parent groups, list only. Reads: list. Writes: none. - `party` — merchants. Reads: list. Writes: create, rename, merge, merge_cluster, delete. - `tag` — transaction tags. Reads: list. Writes: create, rename, delete. - `account` — rename, hide/unhide, tag, note. Reads: list. Writes: add_tag, remove_tag, set_note, remove_note, rename, hide, unhide. - `recurring_series` — bills & subscriptions. Reads: list. Writes: update, rename, delete. - `rule` — transaction rules (preview, then create with the returned token). Reads: list, preview. Writes: create, update, delete. - `plan` — the annual plan (propose a draft from actuals, then upsert). Reads: propose, get, list. Writes: upsert, delete. - `adjustment` — overlay entries that normalize reported actuals without touching transactions. Reads: list. Writes: create, update, delete. - `transfer_link` — match two legs of one transfer. Reads: list. Writes: confirm, reject, link. - `document` — Paper Trail: file, find, read_text, version, tie_out, substantiate, export. Reads: request_upload, list, get, read_text, export, substantiate, tie_out. Writes: create, delete, reconstruct. - `import` — bank statements (OFX/QFX/QBO + Apple Card, Apple Savings, Rocket Money, Wells Fargo, Monarch CSVs). Reads: request_upload, get. Writes: import. - `bank_connection` — list, sync or disconnect bank connections. Reads: list. Writes: sync, disconnect. - `feedback` — file a bug/feature/ux note. Reads: none. Writes: create. - `position` — private holdings (angel checks, fund commitments, advisor equity). Reads: get, list. Writes: create, update, delete. - `position_event` — what moved one (commitment, call, distribution, fee, mark…). Reads: list. Writes: create, update, delete. ## Prompts - `analyze_spending` (Analyze spending): A spending review for a period, optionally focused on one area. - `review_recurring` (Review recurring bills & subscriptions): An audit of recurring charges for duplicates, price rises and savings. ## What changed - 2026-10-11: `account_summary` and summary's account list mark a balance `stale` (with `stale_since` = when the bank last updated it) while the bank has stopped sending updates for that connection — the balance shown is the last one it sent. - 2026-10-11: `bank_connection` sync and list flag a connection whose bank stopped sending updates (`no_update_since`, status `warning`) — `last_sync` keeps moving while it does, so it is not when the bank last updated. - 2026-10-09: New `paycheck_plan` tool: the next few paychecks as a card — what each has to cover before the next one, what to put aside toward bigger bills, and what's left. - 2026-10-08: `bank_connection` sync and list say when the bank last sent new or changed transactions (`last_new`), beside when we last checked (`last_sync`). - 2026-10-08: `bank_connection` sync and list say when each connection is next checked on its own (`next_by`), and a sync says when there was nothing new. Syncing on a schedule is not needed. - 2026-10-07: Connect-time instructions name all four tools and one answer for a household with no bank yet: call the `accounts` tool, else send the person to `setup.url`. - 2026-10-07: Private holdings: `vested_through` is the last vested tranche. - 2026-10-05: Tool descriptions are shorter: duplicated sections and restated schemas removed. - 2026-10-04: `admin`: a rule gets a default name, and `bank_connection` gains `list`. - 2026-10-04: Statements from Chase, Citi, Navy Federal and Wells Fargo cards can be read and checked against the ledger. - 2026-09-30: Grouped, trend and matrix responses state which way their amounts point in a `sign` field, and compare the change in cash (`net_vs_prior`). - 2026-09-29: Tag, recurring-series and position lists page with `limit`/`cursor`. - 2026-09-29: Rule previews flag ignored matches `ign:true` and show the bank memo as `raw`. - 2026-09-12: The echoed `as_of` is the moment of the read, so `since` picks up exactly where the last run stopped. ## Safety - The connection acts only as the signed-in person, only on their own household's data. - Every tool declares whether it reads or writes, so clients run the read-only ones freely and ask before the rest. - Cashflow never edits what the bank reported. Categories, names, tags and rules are kept alongside each transaction, so any change an agent makes can be changed back. - Bulk changes need a preview token, and over 20,000 matches are refused. Turning a statement into transactions is a dry run until asked otherwise. - Every tool call is logged. The demo account is read-only. ## What the server tells every agent on connect The exact instructions the MCP server sends in its `initialize` response. This is machine-facing text, quoted verbatim: ``` Cashflow MCP — personal finance data for one household. TOOL SELECTION: - query: all reads (spending, trends, recurring, forecasts, health checks, explain) - annotate: write classifications onto transactions (categorize, tag, set party, flag) - admin: CRUD for reference entities (categories, parties, rules, tags, accounts) + bank sync + feedback - accounts: interactive card of the connected banks, with sync / reconnect / connect buttons (clients that can show one) - paycheck_plan: the next few paychecks — what each covers before the next lands, what to put aside, what's left (a card where the client can show one; JSON otherwise) AMOUNTS: - All amounts are in the household's currency, in whole units, never cents — dollars for a US household (query responses, annotate filters, admin rule conditions). A household outside USD gets ccy at the root of its responses; a private position's amounts are in that position's own currency. - Detail mode: amt is signed (inflow positive, outflow negative). - Summary: income is money in and expenses are spending, both positive in a normal period (refunds reduce expenses); net is signed. - Every other aggregate response (grouped, time series, multi-dim, tag_report, plan_status, and any reconcile bridge) states its convention in a `sign` field: "inflow+ outflow-" (like detail), "spend+ refund-" (type:"expense"), or sectioned — "inflow+ outflow-; expenses spend+ refund-" for type:"pnl" and the multi-dim matrix, whose expenses section is spend-positive. plan_status reads "spend+ refund-; income in+". COMMON WORKFLOWS: 1. Fix misclassification: query { explain:true, transaction_id } → understand source → annotate { action:"categorize" } or admin to edit the rule. 2. Create rule: admin { entity:"rule", action:"preview", ...conditions, ...actions } → review matches → admin { action:"create", token, name?, ...same conditions, ...same actions } (token from preview; conditions + actions must match what was previewed). 3. Bulk cleanup: annotate { filter, preview:true } → returns { matched, token, preview:[sample] } → re-run the SAME action+filter WITH token to execute. Token is REQUIRED for filter writes; it binds action+household+the exact matched transactions, so a stale token (the matched set, scope, or household changed since preview — including a same-count swap) is rejected. Writes over 20,000 matches are rejected — narrow the filter or pass explicit transaction_ids (which need no token). 4. Recurring audit: query { recurring:true } → identify issues → annotate { action:"set_recurring" } or admin { entity:"recurring_series", action:"update", active:false } to deactivate a stale series. FILTERS: - `query` takes filters as top-level params: { tag: "wechat", category: "Restaurants", search: "uber", period: "this_month" }. Nesting them under `filter` is flattened as a compatibility shim and warned about — don't rely on it. - `annotate` wraps filters in `filter`: { filter: { tag: "wechat", period: "this_month" }, action: "categorize", category_name: "Restaurants" }. period/start/end go INSIDE filter; unknown filter keys are rejected, not dropped. - party and category are exact match (case-insensitive). search is fuzzy across display name, party name, description, check number. category/group/party/tag accept string or string[]. - No date params = all time — for annotate that means an ALL-TIME WRITE, so scope bulk actions with filter.period and check the `range` echoed in the response. NO DATA YET: a response carrying setup.status:"no_accounts" means no bank is connected, so every read comes back empty. Call the accounts tool if your client can show it; otherwise send the person to setup.url. DISCOVERY: Use admin { entity:"category"|"party"|"tag", action:"list" } to look up names/IDs before filtering or annotating (party and tag lists page at 50 — follow cursor while has_more is true). Use the category-hierarchy resource for the full default taxonomy. SCHEDULED / PROACTIVE USAGE (for agents running on a cron or a recurring check): - Daily "what should I flag" check: query { digest:true } — one read returning what needs attention, including the low-balance forecast, missed bills, unusual charges, and any broken bank connections. Bounded and cheap; safe to run every morning. - "What's new since last time": query { detail:true, since:"" } — returns only transactions that arrived since that moment, plus an as_of timestamp to store and pass on the next run (sent only on the last page — page to the end before storing it). - Don't schedule bank syncs: new transactions arrive automatically, and admin { entity:"bank_connection", action:"sync" } on a timer almost always fetches nothing. A scheduled run reads with query { detail:true, since } instead; bank_connection list says how fresh each connection is. - query is read-only and idempotent — safe to run unattended on a schedule. Reserve annotate/admin for when the user has approved a change. ```