--- name: finacontrol description: Use FinaControl's MCP tools to manage personal finances — bank accounts, transactions, transfers, credit cards, sub-transactions, categories, balance adjustments, investment holdings, savings goals, taxes, cash-flow projections, and the monthly dashboard. Use whenever the user asks about their FinaControl data, mentions an account/transaction/credit card/holding by name, or asks anything money-related and a FinaControl MCP server (often named `finacontrol`) is connected. The tools listed below are exposed by that server; do not invent tools that aren't on the list. --- FinaControl is a personal-finance app. Its MCP server exposes 39 tools that let an AI assistant read and modify the signed-in user's finances — bank accounts, transactions, transfers, credit cards, sub-transactions, categories, balance adjustments, investment holdings, savings goals, taxes, cash-flow projections, and the monthly dashboard. This skill teaches you how to use those tools correctly and how to chain them into real workflows. If the FinaControl MCP isn't connected, say so — don't fabricate results. The server's tool names appear in your tool list prefixed by its MCP server name (often `finacontrol`); the bare names below match the `tool_name` registered on the server. If the user wants to connect it, jump to "Connecting the MCP" below. ## Hard rules — get these wrong and you give the user incorrect financial information 1. **All amounts are integer cents.** `amount_cents: 12500` means R$125.00 / $125.00 / €125.00 — the currency comes from the account, not the amount. Never pass floats. When the user says "R$ 89,90", send `8990`. When showing amounts back, divide by 100 and format with the account's currency. 2. **Sign is enforced by the server, not by you.** Always pass a **positive** `amount_cents` to `create_transaction` and `create_sub_transaction`. The server flips the sign for expenses internally. In `list_transactions` / `get_transaction` results, negative `amount_cents` = expense, positive = income — present them as expenses/income, not as "negative numbers". 3. **Dates are `YYYY-MM-DD` strings.** Convert relative phrases ("today", "yesterday", "last Friday", "this month") before calling. Today is whatever the user's current date is — ask if you genuinely don't know, don't guess a year. 4. **Transfers use `create_transfer`, never `create_transaction`.** `create_transaction` only accepts `type: "Income"` or `type: "Expense"`. A move between two of the user's own bank accounts is a transfer and must go through `create_transfer`, which atomically creates the outgoing + incoming pair. 5. **Credit-card expenses go on the card, not the bank account.** When the user says "I bought X with my Visa", call `create_transaction` with `credit_card_id` set and **omit** `account_id`. When they say "I paid X from checking", set `account_id` and omit `credit_card_id`. 6. **Use IDs the server returned to you, in this session.** Don't reuse an ID from an earlier conversation — it may belong to a different account or no longer exist. Resolve names → IDs by calling `list_bank_accounts` / `list_credit_cards` / `list_categories` first. 7. **Granted scope limits what you can do.** If a tool isn't in your tool list, the user didn't grant that scope when they connected (read / write / delete). Tell them — don't suggest workarounds that bypass it. 8. **Confirm before destroying.** `delete_*` and large updates aren't reversible from the AI side. Show the record, summarize what will change, and ask before calling a destructive tool — even if the user said "delete it". ## Tool reference Amounts in **cents**. Dates as **`YYYY-MM-DD`**. Every tool runs as the signed-in user; you can't see other users' data. ### Bank accounts | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_bank_accounts` | Inventory of accounts. Returns id, name, institution, `investment` flag, currency, initial/current balance, active. | — | `active_only` | | `get_bank_account` | One account with current balance. | `id` | — | | `create_bank_account` | New account. Set `investment: true` for an account that can hold investment operations (holdings). | `name` | `institution`, `investment` (boolean, default false), `currency` (`USD`\|`BRL`\|`EUR`, default USD), `initial_balance_cents` (default 0) | | `update_bank_account` | Patch fields. Explicit `null` clears `institution`. | `id` | name, institution, investment, currency, initial_balance_cents, active | | `delete_bank_account` | Removes the account. Fails if it has transactions or credit cards. | `id` | — | ### Transactions (Income / Expense only — Transfers go through `create_transfer`) | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_transactions` | Root transactions only (no sub-transactions). | — | `account_id`, `credit_card_id`, `category_id`, `type` (`Income`\|`Expense`\|`Transfer`), `paid`, `start_date`, `end_date`, `limit` (default 50, capped at 200) | | `get_transaction` | Full record, plus sub-transactions and used/available cents if it has children. | `id` | — | | `create_transaction` | New Income or Expense. | `type` (`Income`\|`Expense`), `description`, `amount_cents` (positive), `occurred_on` | `account_id` (required for Income; optional for Expense), `credit_card_id` (for credit-card Expense — leaves account_id NULL), `category_id` (leaf categories only — `leaf: true` in `list_categories`), `tax_id`, `paid` (default false), `auto_pay`, `notes`, `original_amount_cents` + `original_currency` + `exchange_rate` (foreign-currency conversions), `recurrence` (`monthly`\|`bimonthly`\|`quarterly`\|`semiannual`\|`annual`\|`weekly`) + `recurrence_day_of_month` (1–28) + `recurrence_starts_on` + `recurrence_ends_on` | | `update_transaction` | Patch fields. Explicit `null` clears `category_id`, `tax_id` or `notes`; other fields ignore null. | `id` | description, amount_cents (positive), occurred_on, account_id, credit_card_id, category_id (leaf categories only), tax_id, paid, auto_pay, notes | | `delete_transaction` | Removes the transaction. Sub-transactions cascade. | `id` | — | ### Balance adjustments An adjustment is a special transaction pinned to the **last day of a month** whose amount is computed so the account's running balance lands on a target you specify — use it to reconcile FinaControl to a real statement balance instead of hunting for the missing entry. At most one adjustment per (account, month). | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `create_adjustment` | Reconcile an account to a target balance at month end. `amount_cents` is derived as `target_balance_cents − balance at end of month`. | `account_id`, `month` (`YYYY-MM` or any `YYYY-MM-DD` in the month — snapped to the last day), `target_balance_cents` (signed) | `description` (default "Balance adjustment"), `notes` | | `update_adjustment` | Change an existing adjustment's target (recomputes `amount_cents`), and optionally move it to a different month. | `id`, `target_balance_cents` (signed) | `month`, `description`, `notes` | ### Transfers | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `create_transfer` | Atomic outgoing + incoming pair between two bank accounts. | `source_account_id`, `destination_account_id`, `amount_cents` (positive), `occurred_on` | `description` (default "Transfer") | ### Credit cards | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_credit_cards` | Inventory of cards. | — | `active_only` | | `get_credit_card` | One card with monthly budget + estimated spending. | `id` | — | | `create_credit_card` | New card. Auto-populates monthly root transactions through year-end. | `name`, `bank_account_id`, `closing_day` (1–28), `due_day` (1–28) | `last_four_digits`, `credit_limit_cents`, `budget_percentage` (1–100, default 30) | | `update_credit_card` | Patch fields. Explicit `null` clears `last_four_digits` or `credit_limit_cents`. | `id` | name, bank_account_id, closing_day, due_day, last_four_digits, credit_limit_cents, budget_percentage, active | | `delete_credit_card` | Removes the card. Fails if it has transactions. | `id` | — | | `get_spending_estimate` | Avg spend by category over recent paid root transactions. | `credit_card_id` | `months` (default 3) | ### Sub-transactions (individual purchases under a credit-card "root" transaction) A credit-card root transaction represents the monthly statement bucket; the actual purchases are sub-transactions under it. Their `type` and `credit_card_id` are inherited — you only pass the parent. | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_sub_transactions` | Children of a root, plus parent's used / available cents. | `transaction_id` | — | | `create_sub_transaction` | Add a purchase under a root. | `parent_transaction_id`, `description`, `amount_cents` (positive), `occurred_on` | `category_id`, `paid` (default false) | | `update_sub_transaction` | Patch fields. Explicit `null` clears `category_id`. | `id` | description, amount_cents (positive), occurred_on, category_id, paid | | `delete_sub_transaction` | Removes the sub-transaction. | `id` | — | ### Investments (holdings under an investment bank account) Investments live under a bank account with `investment: true`. A **holding** is one symbol in one account, aggregated from an ordered ledger of buy / sell / distribution **operations**. Each operation also creates a paired cash transaction on the account, so buying moves cash out and selling/distributions move cash in — you don't record that cash leg yourself. Quantities are decimal strings (up to 8 places); every money value is in **cents**. An operation is booked in **its account's currency** and never converted: prices, fees and tax go in exactly as they were charged, and the figures that come back are in that same currency, reported as `currency` on every holding and operation payload. A portfolio spread across currencies therefore returns rows that can't be summed or compared without converting them yourself. | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_holdings` | Portfolio: one row per (investment account, symbol) with `currency`, kind, sectors, quantity held, cost basis, current value, realized/unrealized P&L, and cash income. Rows in different currencies are not comparable. | — | `account_id` (filter to one investment account) | | `get_holding` | One holding's detail plus its full operations ledger with running totals, in the account's `currency`. | `account_id`, `symbol` | — | | `record_investment_buy` | Record a buy. Creates the asset operation + paired cash-out transaction. | `account_id`, `symbol`, `kind` (`stock`\|`reit`\|`etf`\|`fund`\|`bond`\|`crypto`\|`other`), `quantity` (string), `unit_price_cents` (account's currency) | `name`, `sectors` (array of free-text tags — reuse existing spellings), `operation_cost_cents` (fees, default 0), `tax_cents` (default 0), `batch_fee_cents`, `occurred_on` (default today), `notes` | | `record_investment_sell` | Record a sell. Fails if not enough units are held. Kind/name inherit from the prior buy. | `account_id`, `symbol`, `quantity` (string), `unit_price_cents` (account's currency) | `operation_cost_cents`, `tax_cents`, `batch_fee_cents`, `occurred_on` (default today), `notes` | | `record_investment_distribution` | Record a dividend/interest payment. Creates a cash income transaction net of tax. | `account_id`, `symbol`, `gross_cents` (account's currency) | `tax_cents` (default 0), `occurred_on` (default today), `notes` | | `delete_investment_operation` | Remove an operation and its paired cash transaction. Fails if a buy is still needed to cover later sells. | `id` (asset operation id) | — | ### Savings goals A savings goal is a target amount tied to one bank account. Progress isn't stored — "how much is saved" is derived live from that account's **balance growth since `started_on`**, so contributing to the goal just means the account's balance goes up (via normal income/transfers); there's no separate "add to goal" tool. The goal takes its currency from the account. | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_savings_goals` | All goals with target, saved cents, percent complete, and status (`reached`\|`on_track`\|`behind`\|`stalled`). | — | — | | `get_savings_goal` | One goal with full forecast: saved/remaining cents, percent, monthly pace (actual once there's history, else the planned fallback), projected completion date, status. | `id` | — | | `create_savings_goal` | New goal linked to a bank account; saving is measured from `started_on`. | `name`, `bank_account_id`, `target_amount_cents` (positive) | `started_on` (`YYYY-MM-DD`, default today), `target_date` (deadline), `planned_monthly_contribution_cents` (projection seed before there's history, default 0) | | `update_savings_goal` | Patch fields. Explicit `null` clears `target_date`. | `id` | name, bank_account_id, target_amount_cents, started_on, target_date, planned_monthly_contribution_cents | | `delete_savings_goal` | Removes the goal. The linked account and its transactions are untouched. | `id` | — | ### Categories / taxes / projections / dashboard | Tool | Purpose | Required | Optional / notable | | --- | --- | --- | --- | | `list_categories` | User + system categories as a flat list of tree nodes (max 3 levels): each has `parent_id`, `leaf`, and a `path` breadcrumb ("Fixed costs > Housing > Rent"). Only `leaf: true` categories can be assigned to transactions. System ones (Transfer, Credit Card Payment) have `system: true` and shouldn't be reused for arbitrary expenses. | — | — | | `create_category` | New user category, optionally nested (name unique per parent, max depth 3). A parent that already has transactions can't take children. | `name` | `parent_id` | | `list_taxes` | Tax presets. | — | `active_only` | | `get_cash_flow_projections` | Per-month starting/ending balance, projected income/expenses, net flow, status (green/yellow/red). | `account_id` | `months` (default 6) | | `get_financial_summary` | Balances of all active accounts + income/expense totals + paid/unpaid counts for a month. | — | `month` (`YYYY-MM`, default current) | ## Workflow recipes Walk through these patterns when the user's request matches. ### "What's my financial situation?" / monthly overview 1. `get_financial_summary` — totals + balances for this month. 2. If they ask about a specific account's trajectory, follow with `get_cash_flow_projections` for that `account_id`. 3. Format cents as currency and call out any projection rows with `status: "red"` or `"yellow"`. ### Record a one-off expense 1. If the user didn't name an account/card, `list_bank_accounts` + `list_credit_cards` and ask which to use (or pick the obvious match — "my Visa" → the only Visa card). 2. Resolve category: `list_categories` and pick a matching **leaf** node (`leaf: true`) by its `path`. If no match, ask whether to create one with `create_category` (don't silently create). 3. `create_transaction` with `type: "Expense"`, `account_id` **or** `credit_card_id` (never both), positive `amount_cents`, the date. 4. Echo back the created record's id + amount formatted in the account's currency. ### Record income (salary, refund, etc.) `create_transaction` with `type: "Income"`, **`account_id` is required** (income always lands in a bank account, not a card), positive `amount_cents`, `occurred_on`, optional `category_id`. ### Transfer between accounts Never use `create_transaction` with `type: "Transfer"`. Resolve both account IDs via `list_bank_accounts`, then `create_transfer` with `source_account_id`, `destination_account_id`, positive `amount_cents`, `occurred_on`. The result includes both legs (`outgoing` + `incoming`). ### Set up a recurring bill `create_transaction` with `type: "Expense"`, `recurrence` (e.g. `"monthly"`), `recurrence_day_of_month` (1–28), `recurrence_starts_on`, optionally `recurrence_ends_on`. For credit-card bills, use `credit_card_id`; for direct-debit bills, use `account_id`. ### "What did I spend on my Visa last month?" — credit-card statement reconciliation 1. `list_credit_cards` → resolve the card's id. 2. `list_transactions` with `credit_card_id`, `start_date`, `end_date` covering the statement period — you get the root transactions (one per statement month). 3. For each root, `list_sub_transactions` with `transaction_id: ` to get individual purchases plus `used_cents` / `available_cents` against the monthly budget. 4. `get_spending_estimate` with `credit_card_id` if the user wants the category-level average. ### Add a purchase to an existing credit-card statement 1. `list_credit_cards` (if needed) → `list_transactions` with `credit_card_id` for the current month to find the root transaction. 2. `create_sub_transaction` with `parent_transaction_id`, `description`, positive `amount_cents`, `occurred_on`. Type and credit card are inherited — don't pass them. ### Foreign-currency purchase In `create_transaction`, set `amount_cents` to the **converted** amount in the account's currency, and also pass `original_amount_cents`, `original_currency`, and `exchange_rate` so the original number is preserved. Example: $50 USD charged to a BRL account at 5.10 → `amount_cents: 25500`, `original_amount_cents: 5000`, `original_currency: "USD"`, `exchange_rate: 5.10`. ### "Will I run out of money?" — cash-flow planning `get_cash_flow_projections` for the account(s) in question. Report each month's `ending_balance_cents` and call out any `status: "red"` months (negative ending balance) or `"yellow"` (thin margin). If they ask why a month is red, drill in with `list_transactions` filtered by `account_id` and the month's date range. ### Investments — buy, check the portfolio, record a dividend 1. The account must be an **investment** account (`investment: true`). Resolve it via `list_bank_accounts`; if the user has none, offer to `create_bank_account` with `investment: true`. 2. **Buy:** `record_investment_buy` with `account_id`, `symbol`, `kind`, `quantity` (as a string), `unit_price_cents`, and any fees/tax — all in the account's currency, as charged. The paired cash-out transaction is created for you — don't also call `create_transaction`. 3. **Sell:** `record_investment_sell` — it fails if the user doesn't hold enough units, so `get_holding` first if you're unsure of the quantity held. 4. **Dividend/interest:** `record_investment_distribution` with the `gross_cents` and any `tax_cents`; the net lands as cash income. 5. **Review:** `list_holdings` for the whole portfolio, or `get_holding` for one symbol's ledger and P&L. Report cost basis, current value, and realized/unrealized P&L in each row's own `currency` — if the accounts span currencies, don't total them into one number without converting first. 6. **Undo a mistake:** `delete_investment_operation` with the operation id (from `get_holding`'s ledger). It also removes the paired cash transaction and fails if an earlier buy is still needed to cover later sells. ### Savings goals — set one up and check progress 1. **Create:** resolve the funding account via `list_bank_accounts`, then `create_savings_goal` with `name`, `bank_account_id`, and `target_amount_cents`. Add a `target_date` if they gave a deadline and `planned_monthly_contribution_cents` if they said how much they'll set aside monthly — that seeds the projection before any real saving history exists. 2. **Check progress:** `list_savings_goals` for an overview, or `get_savings_goal` for one goal's forecast. Report saved vs. target in the account's currency, percent complete, and the projected completion date; call out a `behind` or `stalled` status. 3. **Remember progress is derived** from the account's balance growth since `started_on` — there's no "deposit into the goal" call. If the user asks to "add R$500 to my goal", that's really income/a transfer into the linked account (`create_transaction` / `create_transfer`); the goal's saved amount follows automatically. ### Bulk corrections `update_transaction` and `update_sub_transaction` only accept one ID at a time. For "mark all March utilities as paid", first `list_transactions` with the right filters, then iterate `update_transaction` per ID. Summarize what you're about to change and ask before kicking off more than ~5 updates. ### Deletes Always preview: `get_transaction` (or `get_bank_account`, etc.) → summarize → ask → then `delete_*`. `delete_bank_account` and `delete_credit_card` fail if children exist; the error tells you. `delete_transaction` cascades to sub-transactions, which is usually fine but worth mentioning. ## Reporting back to the user - Format cents using the account's currency: `R$ 125,00` for BRL, `$125.00` for USD, `€125,00` for EUR. Don't print "12500 cents". - When listing transactions, include date, description, amount, paid status. Group by month or by account when it helps. - After a write, show the new id and a one-line confirmation in the user's currency. Don't dump the full JSON response. - If a tool returns `{ "error": "..." }`, surface the error verbatim and stop — don't retry blindly. Common ones: "Bank account not found" (wrong id or belongs to another user), "Category not found" (likewise), validation messages from ActiveRecord. ## Connecting the MCP If none of the `finacontrol` tools appear in your tool list, the server isn't connected yet. Walk the user through it — don't guess values, ask for what you need. ### What the user needs first The MCP server uses **OAuth 2.1** — there are no API keys to copy or paste. The user just adds the server URL to their client, signs in to FinaControl in the browser, and approves the access on a consent screen. 1. **A FinaControl account.** They sign in at the URL where the app is hosted (e.g. `https://fina-control.com`, or `http://localhost:3000` for self-hosted dev). No account yet? See "Creating a FinaControl account" just below. 2. **The MCP endpoint URL** — it's the same hostname as the web app with the path `/mcp` (e.g. `https://fina-control.com/mcp`). ### Creating a FinaControl account **There is no MCP tool that registers a user** — sign-up happens in the browser, and OAuth requires an account that already exists. So you can't create the account *for* the user, but you can walk them through it. If they say they're new / don't have an account yet, guide them like this: 1. **Open the sign-up page.** It's the app hostname with the path `/users/sign_up` (e.g. `https://fina-control.com/users/sign_up`, or `http://localhost:3000/users/sign_up` for self-hosted dev). Every plan starts with a free trial — no credit card is asked for at sign-up. 2. **Register.** Two ways, the user picks one: - **Email + password** — fill in first name, email, and a password (last name is optional). The password must meet the strength minimum shown on the form. - **"Continue with Google"** — one click, no password to choose. (Google accounts are asked to set a password later, during onboarding.) 3. **Finish the guided onboarding.** After registering, FinaControl walks them through a short setup — preferences, their first bank account, a few categories, optionally investments, and a first transaction. Encourage them to add at least **one bank account** here; the MCP tools need somewhere to put transactions, and an empty account makes the first `list_bank_accounts` come back empty. They can skip steps and add more later from the web UI or via these tools once connected. 4. **Come back and connect the MCP.** Once they're signed in, continue with "Connecting the MCP" below — add the `/mcp` URL to their client and approve the scopes on the consent screen. Don't ask the user for their password or type it anywhere yourself — they enter it directly in the browser. Your job is to hand them the right URL and tell them what to fill in, then confirm they're signed in before moving on to the connection step. ### claude.ai / ChatGPT (web connectors) Add a **custom connector** pointing at the MCP URL (in claude.ai: Settings → Connectors → Add custom connector; in ChatGPT: enable Developer Mode → add connector). The client discovers the OAuth endpoints automatically, opens a FinaControl sign-in, and shows a consent screen where the user approves **read**, **write**, and/or **delete** access. No URL config beyond the `/mcp` address is needed. ### Claude Code (CLI) ```bash claude mcp add --transport http finacontrol https://fina-control.com/mcp ``` Running a FinaControl tool (or `/mcp` in the REPL) triggers the OAuth sign-in in your browser the first time. Verify with `claude mcp list` — `finacontrol` should appear with a green status. If not, see the troubleshooting section. ### Claude Desktop Add the server as a remote/custom connector pointing at `https://fina-control.com/mcp`. Claude Desktop runs the OAuth flow in your browser on first use. ### Scopes — what the user approves on the consent screen - **Read** (`mcp:read`) — answer questions, never change anything. Always granted. - **Write** (`mcp:write`) — record expenses, transfers, create accounts/cards, update fields. Can't destroy anything. - **Delete** (`mcp:delete`) — full control, including `delete_*` tools. Only when the user explicitly wants the assistant to remove records. If a tool you need is missing from your tool list, the user didn't grant that scope — don't suggest workarounds, ask them to reconnect and approve the access you need. ## Troubleshooting — when something goes wrong Errors come back as `{ "error": "..." }` in the tool's JSON response, or as transport-level failures (401, 429, network). Surface the actual error to the user verbatim, then point at the cause. Do not retry blindly — most of these need user action. ### "Unauthorized" / HTTP 401 The OAuth access token expired, was revoked, or belongs to a soft-deleted account. Ask the user to: 1. Reconnect the client so it runs the OAuth sign-in again (tokens expire and are refreshed automatically, but a revoked connection needs re-approval). 2. Confirm the connection still appears under **Settings → Connected apps** (disconnecting it there revokes access). 3. Confirm they're not still inside the 7-day grace window after deleting their account. The client refreshes tokens on its own; if it can't, disconnecting and re-adding the connector forces a fresh sign-in. ### HTTP 429 / "Too Many Requests" The `/mcp` endpoint is rate-limited to 60 requests/minute per IP and 60/minute per token prefix. You've burned through it — usually from a tight loop (bulk update, large reconciliation). Tell the user, wait at least a minute before retrying, and batch fewer operations per turn going forward. ### A tool isn't in your tool list Not a bug — the token's permissions filter the visible tools. `create_*` / `update_*` need `can_write`; `delete_*` need `can_delete`. Tell the user which permission is missing and ask them to issue a new token with the right boxes ticked. Don't try to fake the write with a workaround. ### `{ "error": "Bank account not found" }` (or "Credit card / Category / Transaction not found") Three real causes, in order of likelihood: 1. **Wrong ID.** You used an ID from a prior session or a hallucination. Call the matching `list_*` tool, get fresh IDs, retry. 2. **Wrong owner.** The ID belongs to another user; the server only sees the signed-in user's records. The fix is the same — use a list tool to find the user's actual records. 3. **Deleted.** The record was removed between your previous read and the write. Re-list and reconfirm with the user before retrying. For "Parent transaction not found" specifically, the parent is the **root** transaction for a credit-card statement month — find it with `list_transactions` filtered by `credit_card_id` for the month, not by listing sub-transactions. ### `{ "error": ["Amount cents must be ...", "Description can't be blank", ...] }` ActiveRecord validation messages. Read them — they're plain English. Common ones: | Message | Likely cause | Fix | | --- | --- | --- | | "Description can't be blank" | You omitted `description` on a create/update | Ask the user what to call it; don't invent something generic like "expense" | | "Amount cents must be greater than 0" | Passed `0` or a negative number | Pass a **positive** integer in cents; the server signs it | | "Occurred on can't be blank" / "is not a valid date" | Missing or bad date format | Use `YYYY-MM-DD` strings | | "Closing day must be ≤ 28" / "Due day must be ≤ 28" | Credit-card day field > 28 | The model caps both at 28 — pick 28 if the real day is higher | | "Recurrence day of month must be ≤ 28" | Same as above on a recurring transaction | Cap at 28 | | "Name has already been taken" | Duplicate category name under the same parent | Use the existing one (call `list_categories` to find its ID) instead of creating a new one | | "Category must be a category without subcategories" | The `category_id` points at a parent node | Call `list_categories` and pick one of its `leaf: true` descendants instead | | "Account must exist" on an Income | `create_transaction` with `type: "Income"` and no `account_id` | Income always lands in a bank account; pass `account_id` | ### `{ "error": "Source or destination account not found" }` on `create_transfer` Either the source or destination ID doesn't belong to the user. Call `list_bank_accounts`, resolve both IDs by name, and retry. Note: transfers must be between two **bank accounts** — you can't transfer to or from a credit card via this tool. ### "Will fail if the account has transactions or credit cards" — `delete_bank_account` returns an error The account has dependent records. Options to offer the user: - **Archive instead of delete.** `update_bank_account` with `active: false` hides it from active listings without removing history. - **Move transactions first.** Use `update_transaction` to reassign each affected transaction to another account, then retry the delete. - **Cascade-style purge.** Walk every transaction on the account and `delete_transaction` it (also deletes sub-transactions), then any credit cards via `delete_credit_card`, then the account. **Confirm with the user** before going this route — it's irreversible. `delete_credit_card` behaves the same way — its dependents are transactions, so either move them or delete them first. ### Network / transport errors ("connect ECONNREFUSED", "EAI_AGAIN", TLS errors) The MCP host is unreachable from the client, or the URL is wrong. Ask the user to: 1. Confirm the exact URL they put in the client config (should end in `/mcp`, no trailing slash). 2. If self-hosted, confirm the server is running and reachable from their machine (a `curl /up` should return 200). 3. If the URL is `http://` against a remote host, switch to `https://` — many clients refuse plaintext. ### The MCP is connected but tools aren't being called The client is connected but the model isn't routing to the tools. Usually the user phrased the question in a way that didn't trigger this skill. Confirm the server name is correct in their tool inventory, then ask the user to retry with something explicit like "use the FinaControl MCP to list my bank accounts". If the tool list is empty even with a known-good token, ask them to restart the client — most clients only refresh the tool list on startup. ## What this MCP does *not* do - No multi-user / household sharing — every tool is scoped to the signed-in user. - No live market quotes via MCP — `list_holdings` / `get_holding` report a stored `current_price_cents` (with `price_updated_at`), not a real-time fetch. You record operations; you don't pull prices. - No bank-import / Plaid / OFX — entries are created manually via these tools or the web UI. - No attachments / receipts via MCP. - No bulk endpoints — one record per call. Loop on the client side. If the user asks for something on this list, say it isn't supported and offer the closest thing that is.