Skip to main content
Glama

Copilot Money MCP Server

Query and manage your personal finances with AI using local Copilot Money data

License: MIT Node.js 18+ TypeScript Tests codecov copilot-money-mcp MCP server MCP Registry

Disclaimer

This is an independent, community-driven project and is not affiliated with, endorsed by, or associated with Copilot Money or its parent company in any way. This tool was created by an independent developer to enable AI-powered queries of locally cached data. "Copilot Money" is a trademark of its respective owner.

NOTE

Copilot Money has announced an official MCP server (currently in waitlist, read-only). If a first-party, read-only integration suits your needs, you should strongly consider using it instead of this community project. Learn more and join the waitlist at agent.copilot.money.

This project remains useful if you need write tools (categorize transactions, manage budgets, edit recurrings, etc.), fully offline cache-mode reads with zero network requests, or simply want access today without waiting for the official rollout.

WARNING

The official MCP beta currently does not surface Plaid-connected accounts. Copilot's team has confirmed the limitation and says they're working on a path forward, but there's no timeline. If most of your institutions connect through Plaid, the official MCP will give you an incomplete picture of your finances. This community server is not affected: it reads the app's full local cache (and, in live mode, the same GraphQL API the app uses), so Plaid-connected accounts are included.

Related MCP server: LunchMoney MCP Server

Overview

An MCP server that gives AI assistants access to your Copilot Money personal finance data. It reads from the locally cached Firestore database (LevelDB + Protocol Buffers) on your Mac. Reads are 100% local with zero network requests.

14 cache-mode read tools (or 25 in --live-reads mode: 8 surviving cache + 17 live), plus up to 19 write tools — query and modify transactions, accounts, holdings, balances, categories, recurring charges, budgets, goals, and investment performance. See Tools by Mode below.

Contributors: writes go through an API we don't own. How the repo keeps its model of Copilot's GraphQL surface from silently drifting — the conformance ledger, live smokes, and weekly drift check — is documented in docs/CONFORMANCE_ARCHITECTURE.md.

Privacy First

We never collect, store, or transmit your data to any server operated by this project — we don't have any. See our Privacy Policy for details.

  • No analytics, telemetry, or tracking of any kind

  • Reads are fully local — zero network requests

  • Open source — verify the code yourself

IMPORTANT

Heads up about AI providers. While this server itself runs locally and never sends your data to any server operated by this project, the AI assistant you connect it to (Claude, ChatGPT, Gemini, etc.) will see your Copilot Money data as part of answering your questions. That means your financial data will be transmitted to and processed by the provider of whichever model you choose — Anthropic, OpenAI, Google, or another third party — subject to that provider's own privacy policy and data retention terms.

By using this MCP server with a hosted AI model, you are knowingly sharing your financial data with that AI provider. Only use this tool if you are comfortable with that trade-off. If you are not, consider waiting for an official Copilot Money integration or using a fully local model.

Tools by Mode

This server exposes different tools depending on which CLI flags you enable.

Mode

Flag

What it does

Auth

Network

Tools available

🟢 Default

(none)

Reads from your local LevelDB cache

❌ None

🔌 Zero (offline)

14 cache-mode read + utility tools

🌐 Live reads

--live-reads

Real-time reads via Copilot's GraphQL API; swaps out 6 cache tools and adds 11 live-only ones

🔒 Browser session

🌐 HTTPS per request

25 read tools (8 cache + 17 live)

✍️ Writes

--write

Adds mutation tools (transactions, tags, categories, budgets, recurrings, splits) and turns on --live-reads automatically — writes need server-fresh transaction metadata, so live reads are coupled to write mode

🔒 Browser session

🌐 HTTPS per request

+19 write tools, on top of the 25 live read tools

Passing --write implies --live-reads; you can still pass --live-reads on its own for read-only live access.

📖 See docs/tools-by-mode.md for the full per-tool inventory with status, caveats, and known limitations (goals, stock splits, response-size caps).

⬆️ Upgrading from v2? v3.0.0 changed the shape of read responses — see docs/MIGRATING-v3.md. No tool was renamed or removed; rows got terse and two arguments were retired.


Quick Start

Prerequisites

  • Node.js 18+ (comes bundled with Claude Desktop)

  • Copilot Money (macOS App Store version)

  • Claude Desktop, Cursor, or any MCP-compatible client

  • For --live-reads or --write only: a supported browser logged in to app.copilot.money — the Chromium family, Firefox, or Safari; see docs/graphql-live-reads.md for the list. The server reuses that session's Firebase refresh token. Without it the server still starts, and the first live or write tool call tells the assistant to log in — log in and retry, no restart needed. Default mode needs nothing — it reads the local cache and makes no network requests.

Installation via Claude Desktop

  1. Download the latest .mcpb bundle from Releases

  2. Double-click the .mcpb file to install in Claude Desktop

  3. Restart Claude Desktop

  4. Start asking questions about your finances!

Installation via npm

npm install -g copilot-money-mcp

Then add to your Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "copilot-money": {
      "command": "copilot-money-mcp"
    }
  }
}

Installation for Cursor

  1. Install the package globally:

    npm install -g copilot-money-mcp
  2. Open Cursor Settings (Cmd + ,) > Features > MCP Servers

  3. Add the server configuration:

    {
      "mcpServers": {
        "copilot-money": {
          "command": "copilot-money-mcp"
        }
      }
    }

What You Can Do

Spending Analysis

"How much did I spend on dining out last month?"

"Show me all my Amazon purchases in the last 30 days"

"What are my top 5 spending categories this year?"

Uses get_transactions (or get_transactions_live for fresh data via --live-reads) and get_categories (or get_categories_live), with date ranges, text search, and category filters.

Account Overview

"What's my net worth across all accounts?"

"Show me my checking account balance over the past 6 months, monthly"

"Which bank connections need attention?"

Uses get_accounts (or get_accounts_live for fresh balances via --live-reads), get_balance_history (with optional get_balance_history_live per-account live variant), get_connection_status, and get_networth_live for net-worth-over-time charts.

Investment Portfolio

"What are my current holdings and total returns?"

"Show me the price history for my largest equity holding over the past year"

"What's my current cost basis on META?"

Uses get_holdings (or get_holdings_live for live cost basis), get_investment_prices (or get_investment_prices_live for live per-security price history).

Budgets & Goals

"Am I on track with my budgets this month?"

"How is my emergency fund progressing?"

"Show me my goal history over the past 6 months"

Uses get_budgets (or get_budgets_live via --live-reads), and get_goals / get_goal_history (cache-only — Copilot's GraphQL endpoint doesn't expose goal data).

Subscriptions & Recurring

"What subscriptions am I paying for?"

"How much do I spend on recurring charges per month?"

Uses get_recurring_transactions (or get_recurring_live via --live-reads) and get_upcoming_recurrings_live for next-due unpaid items.

Configuration

Cache TTL

The server caches data in memory for 5 minutes. Configure via environment variable:

# Set cache TTL to 10 minutes
COPILOT_CACHE_TTL_MINUTES=10 copilot-money-mcp

# Disable caching (always reload from disk)
COPILOT_CACHE_TTL_MINUTES=0 copilot-money-mcp

# Write tools resolve account/item ids for a transaction id via a live
# window fetch when the in-memory index (fed by live reads) misses.
# Default window: 13 months. Raise it to edit older transactions:
COPILOT_WRITE_RESOLVE_WINDOW_MONTHS=30 copilot-money-mcp --write

# Write-resolution routing ids (opaque transaction/account/item ids only —
# never amounts or names) persist whenever live reads are enabled (including
# --write mode), keyed by the logged-in account, so cross-session writes
# skip the live window fetch.
# The same switch also disables the persisted split-parent record
# (split-parents.<uid>.jsonl, opaque ids only): delete_transaction then
# remembers a parent split through this server only until it restarts, and
# after a restart relies on the local cache to refuse it (#842).
# Disable both entirely:
COPILOT_DISABLE_PERSISTENT_INDEX=1 copilot-money-mcp --write

You can also refresh manually using the refresh_database tool.

Decode Timeout

For large databases (500MB+), increase the decode timeout (default: 90 seconds):

# Via environment variable
DECODE_TIMEOUT_MS=600000 copilot-money-mcp

# Via CLI flag
copilot-money-mcp --timeout 600000

For databases over 1GB, also increase Node.js memory:

{
  "mcpServers": {
    "copilot-money": {
      "command": "node",
      "args": [
        "--max-old-space-size=4096",
        "/path/to/copilot-money-mcp/dist/cli.js",
        "--timeout", "600000"
      ]
    }
  }
}

Supported Date Periods

The period parameter supports these shortcuts:

this_month last_month last_7_days last_30_days last_90_days ytd this_year last_year

Authentication & Optional Modes

Both --live-reads and --write make authenticated calls to Copilot Money's GraphQL API at app.copilot.money/api/graphql. They require a logged-in browser session against app.copilot.money — the server reads the same Firebase refresh token the web app stores in your browser. Ten browsers are searched — Chrome, Arc, Microsoft Edge, Brave, Vivaldi, Chromium, Opera, Opera GX, Safari, and Firefox — and each Chromium-family browser across every one of its profiles, not just the default one. See PRIVACY.md for exactly which files are read and why the browser-wide Local Storage read is not scoped to Copilot's origin.

On Linux the Linux locations are searched instead: Chrome (~/.config/google-chrome, plus -beta / -unstable), Microsoft Edge (~/.config/microsoft-edge), Brave (~/.config/BraveSoftware/Brave-Browser), Vivaldi (~/.config/vivaldi), Chromium (~/.config/chromium and the snap's ~/snap/chromium/common/chromium), Opera (~/.config/opera), and Firefox (~/.mozilla/firefox, ~/.config/mozilla/firefox where Firefox 147+ creates new profiles when there is no ~/.mozilla, the snap's ~/snap/firefox/common/.mozilla/firefox, and the flatpak's ~/.var/app/org.mozilla.firefox/.mozilla/firefox). The ~/.config locations honour XDG_CONFIG_HOME. Arc, Safari and Opera GX are macOS-only.

Choosing the browser and profile: --browser / --browser-profile

By default the browsers above are tried in that fixed order and the first working session wins. If more than one Copilot account is logged in on the machine — say one person in Brave and another in Chrome — pin the one to use:

Flag

Environment variable

Value

--browser <name>

COPILOT_BROWSER

chrome, arc, edge (or microsoft-edge), brave, vivaldi, chromium, opera, opera-gx (or operagx), safari, firefox — case-insensitive

--browser-profile <name>

COPILOT_BROWSER_PROFILE

Chromium family: the profile directory (Default, Profile 2) or the profile's display name as shown in the browser. Firefox: the profile directory (abcd1234.default-release), the part after its first dot (default-release), or its Name= in profiles.ini. Case-insensitive. A name that matches more than one profile is refused (see below). Prefix dir: to match only the exact, case-sensitive directory name (dir:Profile 1), or name: to match only display names / Name= (a name that itself starts with dir: or name: takes the prefix twice, e.g. name:name:Work). On Linux a Chrome profile name matches in each of the stable, beta and unstable channels, and every match is searched. Requires a browser.

--browser-data-dir <path>

COPILOT_BROWSER_DATA_DIR

A custom user-data root that replaces the browser's default locations — for a browser launched with --user-data-dir, a portable install, or a copied profile. Chromium family: the folder holding Local State and the Default / Profile N dirs (Opera: also its root-layout storage). Firefox: a profiles root (holding profiles.ini and/or the profile dirs, directly or under Profiles/), or a single profile directory (one holding storage/default, prefs.js or times.json). Use an absolute path: a leading ~ is expanded and a relative path resolves against the server's working directory, which MCP hosts often set to /. Requires a browser; not supported for Safari.

Each flag wins over its environment variable. With a browser selected, only that browser (and, with a profile, only that profile) is read — there is no fallback to the others, so the session can never silently come from someone else's browser. If nothing is found, the error names exactly what was searched, e.g. No Copilot Money session found in Brave (profile "Default"). …, and says so when the browser's data directory or the profile does not exist.

A profile name must pick exactly one profile. Display names are not unique, and Microsoft Edge's built-in ones are off by one from its directories (directory Default is shown as "Profile 1", directory Profile 1 as "Profile 2"), so a name that matches one profile's directory and another's display name — or two display names, or two Firefox profiles — is refused, and nothing is searched: --browser-profile "Profile 1" is ambiguous in Microsoft Edge: it matches directory "Profile 1" and the display name of directory "Default". Pick exactly one with --browser-profile "dir:Default" or "dir:Profile 1". Every value the refusal offers selects exactly one profile: a Firefox profile that profiles.ini keeps outside the profiles directory is offered as name: instead, since dir: only sees listed directories, and two Firefox profiles that no name tells apart (the same directory name directly in a --browser-data-dir and under its Profiles/, or two profiles.ini entries with one Name= kept outside the profiles directory) are named by path: select one by dropping --browser-profile and pointing --browser-data-dir at it. The dir: form always names exactly one profile, so on a stock Edge with three or more profiles — where every middle Profile N is both a directory and another profile's display name — every profile stays selectable. A directory whose own display name is the same string is not ambiguous. On Linux, an ambiguity inside any one Chrome channel refuses the whole selection, and the message names that channel's directory.

The selection only applies with --live-reads or --write. There, an unknown browser name, a profile without a browser, a macOS-only browser on Linux, or a bad --browser-data-dir stops the server at startup with an error. In the default cache-only mode no browser is read, so a selection — from flags or from the environment, valid or not — is ignored with a one-line warning on stderr instead of stopping the server.

With --browser-data-dir, only that directory is read — not the browser's default locations and not any other browser. --browser-profile then matches profiles inside it (directory name, Local State display name, Firefox suffix or profiles.ini Name=, with relative Path= entries resolved against the data dir). Pointed at a single Firefox profile, no --browser-profile is needed, and one that names a different profile is refused. The server refuses to start if the path does not exist or is not a directory, if no browser is given, or if the browser is Safari (whose locations are fixed by macOS). If the directory holds no session, the error names it: No Copilot Money session found in Chrome (data dir "/home/me/chrome-profile/Fork-18"). …, and if the path looks like one Chromium profile (it is named Default / Profile N or holds a Preferences file) rather than the user-data root above it, the error says so and suggests the parent plus --browser-profile.

{
  "mcpServers": {
    "copilot-money": {
      "command": "copilot-money-mcp",
      "args": ["--live-reads", "--browser", "brave", "--browser-profile", "Default"]
    }
  }
}

Or, equivalently, "env": { "COPILOT_BROWSER": "brave", "COPILOT_BROWSER_PROFILE": "Default" } in the same entry. For a Chrome started with a custom --user-data-dir:

"args": ["--live-reads", "--browser", "chrome", "--browser-data-dir", "/home/me/chrome-profile/Fork-18"]

Default mode requires no authentication and makes zero network requests — reads come from the local LevelDB cache.

--live-reads: real-time reads via GraphQL

copilot-money-mcp --live-reads

Replaces 6 cache-mode read tools (get_transactions, get_accounts, get_categories, get_budgets, get_recurring_transactions, get_holdings) with live GraphQL-backed equivalents, and adds 11 net-new ones (get_tags_live, get_networth_live, get_upcoming_recurrings_live, get_monthly_spend_live, get_balance_history_live, get_investment_prices_live, refresh_cache, get_investment_allocation_live, get_top_movers_live, get_aggregated_holdings_live, get_investment_balance_live).

Use this when:

  • You need transactions the macOS app hasn't pre-fetched yet (the auto-fetch window is typically ~30 days; past that, open the app and scroll back to force the cache to populate, or use --live-reads to query the server directly).

  • You want fresh per-security cost basis or balance-over-time data.

  • The macOS app hasn't synced recently.

--write: mutations via GraphQL (implies --live-reads)

copilot-money-mcp --write

Adds 19 mutation tools for transactions, tags, categories, recurrings, budgets, and split-transactions. Off by default — the server is read-only unless you opt in.

--write automatically enables --live-reads as well. Write tools resolve transaction metadata (account/item IDs) against the live GraphQL surface so they can edit any transaction the API exposes, not just the ~30 days the local LevelDB cache happens to hold. Once you've consented to the authenticated network calls writes require, there's no privacy or perf reason to keep reads pinned to the stale cache.

copilot-money-mcp --write --live-reads   # equivalent to `--write` alone; --live-reads is redundant

Configuring via Claude Desktop / Cursor

Add the flags to the args array in your MCP config:

{
  "mcpServers": {
    "copilot-money": {
      "command": "copilot-money-mcp",
      "args": ["--write"]
    }
  }
}

Restart Claude Desktop / Cursor after editing.

Known Limitations

Local Cache Dependency

This server reads from Copilot Money's local Firestore cache, not the cloud. Firestore's offline persistence caches every document the app has ever fetched, so the local database generally contains all transactions, accounts, budgets, goals, and other data you've viewed in the app. The default Firestore cache size is 100 MB (enough for tens of thousands of transactions), and older documents are only evicted via LRU garbage collection if that limit is exceeded.

To maximize cached data: Open the Copilot Money app and browse through your data (transaction history, accounts, budgets) to ensure it has been fetched and cached locally.

Goals are read-only

get_goals and get_goal_history work (cache-only), but there are no goal write tools — Copilot's GraphQL endpoint doesn't expose goal mutations. Goal creation, editing, and contributions are mobile-only in Copilot itself, and live in a path our project can't reach without iOS / desktop traffic capture.

Investment splits are limited to currently-held securities

get_investment_splits returns split events (date + adjustment multiplier) for securities you currently hold. Securities you no longer hold eventually fall out of the cache. There's no GraphQL endpoint for splits, so this is the only path.

Also: get_investment_prices and get_investment_prices_live already return split- and dividend-adjusted prices (Copilot applies Plaid's adjustment factors server-side). You generally don't need raw split events to back-correct prices.

Live investment prices are ownership-gated

get_investment_prices_live only works for securities currently in your linked accounts. Asking for a price series on a ticker you don't own returns an explicit "not in your linked accounts" error.

Long time-series responses are capped

Time-series live tools (get_balance_history_live, get_networth_live, get_investment_prices_live) cap responses at 500 rows by default to fit the MCP single-tool-result token limit. Pass max_rows / offset to paginate, or narrow time_frame for fewer rows.

get_investment_balance_live has its own, much tighter cap under a different name: history_limit, default 30 points, 0 for the whole series. Its responses always report history_total_count and history_truncated, and its current value is resolved separately so it can never be lost to truncation.

Troubleshooting

Database Not Found

If you see "Database not available":

  1. Ensure Copilot Money is installed and has synced data

  2. Check the database location: ~/Library/Containers/com.copilot.production/Data/Library/Application Support/firestore/__FIRAPP_DEFAULT/copilot-production-22904/main

  3. Verify .ldb files exist in the directory

  4. Provide a custom path: copilot-money-mcp --db-path /path/to/database

Decode Worker Timed Out

If you see "Decode worker timed out":

  1. Increase the timeout: copilot-money-mcp --timeout 300000 (5 minutes)

  2. For 1GB+ databases, also increase Node.js memory: node --max-old-space-size=4096 dist/cli.js --timeout 300000

No Transactions Found

  • Open the Copilot Money app and wait for sync

  • The database structure may have changed — open an issue

Contributing

See CONTRIBUTING.md for development setup, architecture, and how to add new tools.

License

MIT License - See LICENSE for details.

Acknowledgments

  • Built with MCP SDK by Anthropic

  • Data validation with Zod

  • Developed with Bun

Available Tools

14 tools
get_accountsA
Read-only

Get all accounts with balances, plus summary fields: total_balance (net worth = assets minus liabilities), total_assets, and total_liabilities. Optionally filter by account type (checking, savings, credit, investment). Checks both account_type and subtype fields for better filtering (e.g., finds checking accounts even when account_type is 'depository'). By default, hidden accounts are excluded. name is your Copilot nickname when set, else the provider label — it is user-editable, so key on account_id, never on name. Default rows are terse: account_id, name, account_type, subtype, current_balance, institution_name, iso_currency_code, item_id, plus user_hidden / user_deleted when set (the flags include_hidden controls). That drops the other ~35 document fields. PARTIAL list of what goes, not exhaustive: the embedded holdings array (get_holdings covers it), institution logo images plus their logo_content_type (base64-encoded, several KB each — this PAIR is where the removed include_logos flag went, and the flag gated both, so request them together), mask (the last-4 digits humans identify an account by), available_balance, limit, institution_id, creation_timestamp (when the account was created in Copilot, YYYY-MM-DD — written only on newer rows, so absent means unknown, not old), the denormalized official_name / original_* name dupes, and user_id. Any document field is requestable by name: fields: ["default", "mask", "available_balance"], or "all" / "*" for full rows. include_logos was removed in v3.0.0 — passing it now raises an error naming this migration.

NameRequiredDescriptionDefault
fieldsNoReturn only these fields per account row (e.g. ["account_id", "name", "current_balance"] for get_accounts, or ["id", "name", "balance"] for get_accounts_live — field names differ between the two modes). "default" expands to a terse baseline covering identity, name, type, balance, institution, and item — see each tool's own description for its exact field list and what "default" excludes. "all" or "*" returns the full row. Unknown names are omitted and reported via _field_warning.
account_typeNoFilter by account type (checking, savings, credit, loan, investment, depository). Note: summary totals (total_assets, total_liabilities, total_balance) reflect only the filtered subset.
include_hiddenNoInclude hidden accounts (default: false)

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true already covering safety, the description adds substantial behavioral context: default rows omit roughly 35 fields, hidden accounts are excluded unless include_hidden is set, name is user-editable and must not be used as a key, and include_logos was removed in v3.0.0 and now raises an error.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose, filtering behavior, and default projection are front-loaded, and most detail is relevant. The long parenthetical inventory of excluded fields is informative but dense, making the description longer than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, but the description specifies the default return fields, the summary fields, the kinds of fields omitted by default, and how to request any document field by name. Combined with readOnlyHint and full schema coverage, this is enough for an agent to call the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description meaningfully expands parameter semantics. It explains how fields supports 'default', 'all', '*', and explicit names; how account_type filtering checks both account_type and subtype; and how include_hidden controls the user_hidden/user_deleted flags.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Get all accounts with balances, plus summary fields.' It also clarifies scope and identity versus siblings by noting that embedded holdings are covered by get_holdings and by explaining the default field projection.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is clear: optional filtering by account type, hidden accounts excluded by default, and fields projection via 'default', 'all', or explicit names. It also routes the agent away for holdings data by naming get_holdings, though it does not systematically compare against all relevant siblings such as get_balance_history.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_balance_historyA
Read-only

Get daily balance snapshots for accounts over time. Each entry returns current_balance, available_balance, limit, account_id, and account_name. The response also includes an accounts array listing the distinct account IDs in the paginated page. Requires a granularity parameter (daily, weekly, monthly) to control response size. Weekly and monthly modes downsample by keeping the last data point per period. Filter by account_id and date range.

NameRequiredDescriptionDefault
limitNoMaximum number of results (default: 100, max: 10000)
offsetNoNumber of results to skip for pagination (default: 0)
end_dateNoEnd date (YYYY-MM-DD)
account_idNoFilter by account ID
start_dateNoStart date (YYYY-MM-DD)
granularityYesRequired. Controls response density: daily (every day), weekly (one per week), or monthly (one per month). Use weekly or monthly for longer time ranges.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

readOnlyHint=true is already covered by annotations, so the description is free to add real value: it discloses downsampling behavior (keeping the last data point per period) and that the accounts array reflects the paginated page. These are non-obvious behavioral traits beyond the annotations, though pagination/limit interaction and freshness are not spelled out.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then return shape, then the granularity/downsampling caveat. Every sentence carries information; only the restatement of the required granularity slightly overlaps with the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by enumerating the returned fields (current_balance, available_balance, limit, account_id, account_name) and the accounts array. It covers the key non-obvious behavior (downsampling), leaving only secondary details like cache vs live data and error/empty behavior unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining the semantic effect of granularity (downsampling via last data point per period) beyond the enum values, and noting account_id/date-range filtering. This adds meaning the schema alone does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Get daily balance snapshots for accounts over time,' which distinguishes it from siblings like get_transactions, get_goal_history, and get_investment_prices that return different resource types. It doesn't name siblings explicitly, but the resource is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives useful intra-tool guidance ('use weekly or monthly for longer time ranges'), but offers no explicit when-to-use-this-vs-alternatives guidance relative to the many sibling read tools, nor any prerequisites/exclusions. Usage is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_budgetsA
Read-only

Get budgets from Copilot's native budget tracking. Returns the current-month effective budget per category plus the full amounts map of per-month overrides for history lookups. For parent categories, the returned amount is the resolved total (children + rollovers) that Copilot displays in the Budgets view. Totals use the current-month effective amount.

NameRequiredDescriptionDefault
active_onlyNoOnly return active budgets (default: false)

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate readOnlyHint=true. The description adds value by detailing that budgets are from native tracking, returns effective budget per category and amounts map, and explains resolved totals for parent categories. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the main action, and every sentence adds necessary information. No redundancy or wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema, but the description compensates by explaining return values (effective budget, amounts map, resolved totals). It provides sufficient context for a simple read operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers 100% of the single parameter (active_only) with a clear description. The tool description adds no additional meaning to the parameter beyond what the schema provides, so baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it retrieves budgets from Copilot's native budget tracking, specifies the return content (current-month effective budget per category plus full amounts map), and explains behavior for parent categories. It distinguishes itself from sibling tools by focusing on budgets.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for budget retrieval but does not explicitly state when to use this tool versus alternatives like get_transactions or get_categories. No exclusions or when-not-to-use guidance is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_cache_infoA
Read-only

Get information about the local data cache, including the date range of cached transactions and total count. Useful for understanding data availability before running historical queries. This tool reads from a local cache that may not contain your complete transaction history. Also reports decode_health: per-collection counts of documents dropped on schema validation failure (a "degraded" status means some cached documents are missing from results).

NameRequiredDescriptionDefault

No parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes beyond readOnlyHint=true by disclosing that the tool reads a local cache that may not contain complete transaction history, and by defining what a "degraded" decode_health status means (documents dropped on schema validation). A useful caveat about the remediation path (refresh_database) is missing, but the data-quality semantics are well disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with what the tool returns before the usage hint and the caveats. The parenthetical explaining decode_health is dense but earns its place by preventing misinterpretation of returned fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description carries the return-value burden, and it names the key fields (date range, count, per-collection decode_health) along with their interpretation. The only meaningful omission is guidance on what to do when the cache is incomplete or degraded.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is nothing for the description to compensate for and it makes no claims about inputs that could mislead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: reports cache metadata (date range of cached transactions, total count) plus decode_health counters. This is clearly distinguishable from sibling data-fetching tools like get_transactions because it describes availability metadata rather than transaction records, though it never explicitly names those siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Useful for understanding data availability before running historical queries" gives a concrete when-to-use trigger. It stops short of explicit alternatives or exclusions — notably it never points to refresh_database when the cache is found to be stale or incomplete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_categoriesA
Read-only

Unified category retrieval tool. Supports multiple views: list (default) - user categories with transaction counts/amounts for a time period; tree - user categories as hierarchical tree; search - search user categories by keyword. Use parent_id to get subcategories. For list view, use period (e.g., "this_month") or start_date/end_date to filter by date. Includes all categories, even those with $0 spent (matching UI behavior).

NameRequiredDescriptionDefault
viewNoView mode: list (categories with spend totals), tree (parent/child hierarchy), search (find by keyword)
queryNoSearch query (required for 'search' view)
periodNoTime period for list view (e.g., 'this_month', 'last_month', 'last_30_days', 'this_year'). Takes precedence over start_date/end_date if provided.
end_dateNoEnd date for list view (YYYY-MM-DD format)
parent_idNoGet subcategories of this parent category ID
start_dateNoStart date for list view (YYYY-MM-DD format)

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide readOnlyHint: true. The description adds that the tool includes categories with $0 spent, matching UI behavior, and explains the behavior of different views. This goes beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is fairly concise given the complexity of three views and multiple parameters. It is front-loaded with the main purpose. Minor redundancy could be trimmed, but overall efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers all major aspects: views, filtering, parent_id, date options, and the inclusion of zero-spend categories. Despite no output schema, the description is sufficient for an agent to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers all parameters with descriptions (100% coverage). The description adds extra meaning by explaining that categories with $0 spent are included, and that period takes precedence over dates, which is not in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Unified category retrieval tool' and explains three distinct views (list, tree, search) with specific use cases. It distinguishes itself from sibling tools like get_accounts and get_transactions by focusing on category data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use each view (e.g., list for spend totals, tree for hierarchy, search for keyword) and how to filter by date or parent_id. However, it does not explicitly state when not to use this tool or mention alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_connection_statusA
Read-only

Get connection status for all linked financial institutions. Shows per-institution sync health including last successful update timestamps for transactions and investments, login requirements, and error states. Use this to check when accounts were last synced or to identify connections needing attention. Also reports decode_health: per-collection counts of cached documents dropped on schema validation failure (a "degraded" status means some documents are missing from results). Also reports scheduled_smoke: the last scheduled API-drift check with timestamp, or null if the weekly job is not installed. Only "fail" means the API drifted; "auth-missing" and "incomplete" mean drift is unknown.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true already covering safety, the description adds substantial behavioral detail: per-institution sync health, timestamps, login requirements, error states, decode_health semantics, scheduled_smoke behavior, and the meaning of fail vs auth-missing/incomplete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the main purpose and then adds return-value semantics, which are important because there is no output schema. It is dense but every sentence contributes; it could be slightly more structured, but it is not padded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters, no output schema, and only a readOnly annotation, the description carries the return-value burden well. It explains the main status payload and the auxiliary decode_health and scheduled_smoke fields, including status interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there are no parameter semantics to explain. Baseline for a no-parameter tool is 4, and the description correctly does not invent parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: get connection status for linked financial institutions. It then scopes the response to sync health, login requirements, error states, decode_health, and scheduled_smoke, making the tool clearly distinct from the data-retrieval siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to use this to check when accounts were last synced or to identify connections needing attention. That gives clear positive usage context, but it does not name alternatives or state when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_goal_historyA
Read-only

Get monthly progress snapshots for financial goals. Returns current_amount, target_amount, daily data points, and contribution records per month. Filter by goal_id or month range (YYYY-MM). Cache-only: no live-mode (--live-reads) counterpart exists because Copilot's GraphQL endpoint does not expose goal data, so this tool always returns cached LevelDB data regardless of the --live-reads flag.

NameRequiredDescriptionDefault
limitNoMaximum number of results (default: 100, max: 10000)
offsetNoNumber of results to skip for pagination (default: 0)
goal_idNoFilter by goal ID
end_monthNoEnd month (YYYY-MM)
start_monthNoStart month (YYYY-MM)

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds significant behavioral context beyond the readOnlyHint annotation, explaining that the tool is always cached, ignores the --live-reads flag, and why (backend limitation). This helps the agent understand the tool's data freshness and constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, each serving a purpose: purpose, return fields, and cache behavior. It is front-loaded with the core function, then details, then important behavioral note. No unnecessary words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 5 optional parameters and no output schema, the description covers purpose, return fields, filtering, and cache behavior. It does not explain pagination parameters (limit/offset) but those are standard and described in the schema. Overall, it provides enough context for the agent to select and use the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so the description doesn't need to repeat parameter details. However, it adds value by clarifying that the tool returns 'daily data points' and 'contribution records per month,' which are not in the schema. This enriches the agent's understanding of the output.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves monthly progress snapshots for financial goals, listing specific return fields (current_amount, target_amount, daily data points, contribution records). It distinguishes from siblings like get_goals or get_balance_history by focusing on monthly history snapshots.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states this tool is cache-only and always returns cached data, with no live-mode counterpart. It explains the reason (GraphQL endpoint does not expose goal data), guiding the agent on when to use this tool versus others. It could be improved by explicitly stating when not to use, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_goalsA
Read-only

Get financial goals from Copilot's native goal tracking. Retrieves user-defined savings goals, debt payoff targets, and investment goals. Returns goal details including target amounts, monthly contributions, status (active/paused), start dates, and tracking configuration. Calculates total target amount across all goals. Cache-only: no live-mode (--live-reads) counterpart exists because Copilot's GraphQL endpoint does not expose goal data, so this tool always returns cached LevelDB data regardless of the --live-reads flag.

NameRequiredDescriptionDefault
active_onlyNoOnly return active goals (default: false)

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses beyond readOnlyHint: always returns cached LevelDB data regardless of --live-reads flag, alerting the agent to staleness. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences efficiently convey purpose and key constraint. Could be slightly more structured with bullet points, but no waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given simple read-only tool with one parameter and no output schema, description covers all essential aspects: goals covered, fields returned, cache limitation, and no live mode.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema has 1 parameter with full description. Description adds no new info beyond schema, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it retrieves financial goals from Copilot's native goal tracking, listing types (savings, debt, investment) and details returned. It distinguishes from siblings like get_accounts and get_budgets.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly mentions cache-only nature and lack of live-mode counterpart, indicating when to use. No explicit alternatives among siblings, but context implies this is the only goal tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_holdingsA
Read-only

Get current investment holdings with position-level detail. Returns ticker, name, quantity, current price, equity value, average cost, and total return per holding. Joins data from account holdings, securities, and optionally historical snapshots. Filter by account or ticker symbol. Note: cost_basis may be unavailable for cash-equivalent positions. Positions on hidden and merged accounts are EXCLUDED by default, matching get_accounts — pass include_hidden: true for them. Do NOT sum holdings as portfolio value: unvested stock-plan shares are reported as holdings, so the sum can far exceed the investment balance; with --live-reads use get_investment_balance_live for the total.

NameRequiredDescriptionDefault
limitNoMaximum number of results (default: 100, max: 10000)
offsetNoNumber of results to skip for pagination (default: 0)
account_idNoFilter by investment account ID. Returns empty for a hidden or merged account unless include_hidden is also set.
ticker_symbolNoFilter by ticker symbol, case-insensitive (e.g., "AAPL", "SCHX"). The cash sleeve is CUR:USD here and USD in live mode; either spelling matches it in both modes (CUR:USD matches only cash; a bare USD also matches a security tickered USD).
include_hiddenNoInclude positions on hidden and merged accounts (default: false). Same flag, same default as get_accounts — leave it off and the two tools agree on which accounts exist.
include_historyNoInclude monthly price/quantity snapshots per holding (default: false)

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true. The description adds meaningful behavioral context beyond that: hidden/merged accounts are excluded by default, cost_basis may be unavailable for cash-equivalents, and unvested stock-plan shares inflate the sum. Minor gap on pagination behavior, but the safety and edge-case profile is well covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and return fields, then adds scoping and caveats in order of importance. Every sentence carries a distinct fact (return fields, join sources, filtering, hidden-account behavior, summation warning). Slightly dense but no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, but the description enumerates the returned fields so an agent knows the shape. Combined with the behavioral caveats and filtering guidance, the definition is complete enough to call correctly, with only pagination left to the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all six parameters thoroughly. The description restates filtering by account/ticker and the include_hidden alignment with get_accounts, adding only marginal value beyond what the schema provides. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Get) and resource (current investment holdings) at position-level detail, then enumerates the return fields (ticker, quantity, price, equity value, cost, return). This distinguishes it cleanly from siblings like get_transactions, get_investment_prices, and get_investment_splits.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when filtering applies (account/ticker), explains the include_hidden default matches get_accounts so the two agree, and gives a clear when-not-to-use with an alternative: 'Do NOT sum holdings... use get_investment_balance_live for the total.' This is exactly the routing guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_investment_pricesA
Read-only

Get investment price history for portfolio tracking. Returns one row per (security, period) for stocks, ETFs, mutual funds, and crypto. Filter by ticker symbol, date range, or price type (daily/hf). By default each row is TERSE: the security, the period, and its latest price (latest_price / latest_at) — enough to answer "what is this worth". The full price series (prices, an epoch-millis map) is most of a full row and roughly quadruples response size, so it is omitted unless requested via fields; ask for it only when you need the time series itself, not a current value.

NameRequiredDescriptionDefault
limitNoMaximum number of results (default: 100, max: 10000)
fieldsNoReturn only these fields per row. Default when omitted: a terse row (security_id, ticker_symbol, price_type, date/month, latest_price, latest_at). The full intraday/daily series lives in `prices`, which is EXCLUDED by default because it is most of a row and multiplies response size several-fold — request it explicitly with fields: ["default", "prices"], or use "all" / "*" for full rows. Unknown names are omitted and reported via _field_warning.
offsetNoNumber of results to skip for pagination (default: 0)
end_dateNoEnd date (YYYY-MM-DD)
price_typeNoFilter by price type: daily (monthly aggregates) or hf (high-frequency intraday)
start_dateNoStart date (YYYY-MM-DD)
ticker_symbolNoFilter by ticker symbol (e.g., "AAPL", "BTC-USD", "VTSAX")

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint; the description goes well beyond that by disclosing default projection (terse rows), the specific fields returned, and that `prices` 'roughly quadruples response size' and is excluded unless requested. That is exactly the cost/behavior signal an agent needs before calling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and row shape, then the default-terse behavior, then the opt-in cost. Five sentences all carry information, though the `prices`/response-size point is restated in both the description and the schema, slightly inflating it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description compensates fully: it describes the returned row shape, the default field set, the `latest_price`/`latest_at` semantics, and the shape of `prices` (epoch-millis map). An agent can invoke and interpret this tool from the description alone.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description still adds value by explaining the why behind `fields` (full series omitted by default, how to opt in) and by framing ticker/date/price_type as filters. Some of this duplicates the schema's own `fields` text, which keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Get investment price history') plus the row granularity ('one row per (security, period)') and the covered asset classes. An agent knows exactly what dataset comes back and can tell it apart from holdings/splits tools without reading the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-request guidance for the expensive `prices` field: 'ask for it only when you need the time series itself, not a current value.' It does not name sibling alternatives (e.g. get_holdings for current positions), so routing between tools is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_investment_splitsA
Read-only

Get stock split events from the local Firestore cache. Returns one row per (security, effective_date) with the adjustment multiplier (e.g. 0.1 for a 10-for-1 split — multiply pre-split prices/quantities by this value to convert to the post-split equivalent). Joined with the securities collection so each row includes ticker and name. IMPORTANT: prices returned by get_investment_prices and get_investment_prices_live are ALREADY split-adjusted by Copilot. Use this tool only when you need the split events themselves (e.g., for narrative or historical-analysis purposes) — you do NOT need to apply these multipliers to the prices yourself. Securities that have never split are not included in the output. Coverage is limited to securities Copilot currently syncs in your local cache (typically currently-held or recently-held).

NameRequiredDescriptionDefault
limitNoMaximum number of rows. Default 100, max 10000.
offsetNoPagination offset, default 0.
end_dateNoOptional. Inclusive upper bound on effective_date (YYYY-MM-DD).
start_dateNoOptional. Inclusive lower bound on effective_date (YYYY-MM-DD).
ticker_symbolNoOptional. Case-insensitive ticker filter (e.g. "NVDA").

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds details about output structure (one row per security/date), the meaning of the multiplier, the join with securities, and cache coverage. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and details, then usage guidance. It is somewhat lengthy but all sentences add value. Could be slightly more concise, but structure is logical.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description fully explains the output structure, including the multiplier meaning and joined fields. All 5 parameters are well-documented. The tool's behavior is completely described.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with good descriptions, but the description adds extra context (e.g., case-insensitive ticker, inclusive date bounds, effective_date field name). This enhances clarity beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it retrieves stock split events from the local Firestore cache, specifying verb 'get', resource 'stock split events', and scope. It distinguishes from siblings like get_investment_prices by noting that those return already-adjusted prices.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells when to use this tool ('when you need the split events themselves') and when not to ('you do NOT need to apply these multipliers to the prices yourself'). Also mentions coverage limitations, guiding the agent appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_recurring_transactionsA
Read-only

Identify recurring/subscription charges. Combines two data sources: (1) Pattern analysis - finds transactions from same merchant with similar amounts, returns estimated frequency, confidence score, and next expected date. (2) Copilot's native subscription tracking - returns user-confirmed subscriptions stored in the app. Both sources are included by default for comprehensive coverage. Rows in the pattern-analysis recurring array are terse by default: merchant, normalized_merchant, occurrences, average_amount, total_amount, frequency, confidence, category_name, last_date, next_expected_date. That excludes the matched transactions array (already reachable via get_transactions) and confidence_reason (prose explaining the score) — see fields for how to get either back.

NameRequiredDescriptionDefault
nameNoFilter by name (case-insensitive partial match). When filtering, returns detailed view with additional fields like min_amount, max_amount, match_string, account info, and transaction history.
fieldsNoReturn only these fields per detected recurring merchant. Default when omitted omits two expensive fields: `transactions` (the matched date/amount pairs, ~29% of a row) and `confidence_reason` (prose explaining the confidence score, ~15%). `confidence` itself is always in the default. Request the rest with fields: ["default", "transactions", "confidence_reason"], or "all" / "*" for full rows. Unknown names are omitted and reported via _field_warning.
periodNoPeriod to analyze (default: last_90_days). Options: this_month, last_month, last_7_days, last_30_days, last_90_days, ytd, this_year, last_year
end_dateNoEnd date (YYYY-MM-DD)
start_dateNoStart date (YYYY-MM-DD)
recurring_idNoFilter by exact recurring ID. When filtering, returns detailed view with additional fields like min_amount, max_amount, match_string, account info, and transaction history.
min_occurrencesNoMinimum number of occurrences to qualify as recurring (default: 2)
include_copilot_subscriptionsNoInclude Copilot's native subscription tracking data (default: true). Returns copilot_subscriptions array with user-confirmed subscriptions.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint, so the heavy lifting is on the description — and it delivers: default rows are a projection that omits two named expensive fields with rough payload shares, and it discloses the _field_warning mechanism for unknown names. It also notes that filtering by name/recurring_id switches to a 'detailed view'. Missing any guidance on result-set size or how many merchants a broad period can return, so not a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the purpose and the source split before the payload detail, which is the right ordering. The `fields`/default-projection exposition is somewhat redundant with the schema's own description of the same parameter, costing a few lines of duplicated content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

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 properly enumerates the default row fields and explains what is excluded — enough for an agent to know what it will get back. The shape of the copilot_subscriptions rows and the size of the combined payload under a broad period are left unspecified, but the core contract is covered for a 0-required-param read tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter including the fields projection language and the period enum values. The description largely restates the `fields` default behavior that the schema also explains, adding emphasis rather than new meaning. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Identify recurring/subscription charges') and immediately decomposes the tool into two named data sources with distinct semantics (pattern analysis vs. user-confirmed Copilot subscriptions). It also names the sibling that already owns the matched transaction data (get_transactions), so an agent can tell it apart from the other read tools in the set.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states both sources are included by default for 'comprehensive coverage' and points to get_transactions as the route for matched transaction detail, which is real routing guidance. It stops short of saying when to prefer one source over the other or when to disable include_copilot_subscriptions, so it does not reach full when/when-not coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_transactionsA
Read-only

Reads from the local LevelDB cache, which may lag behind Copilot's server if the macOS app hasn't synced recently. For real-time data use --live-reads with get_transactions_live. Unified transaction retrieval tool. Supports multiple modes: (1) Filter-based: Use period, date range, category, merchant, amount filters. (2) Single lookup: Provide transaction_id to get one transaction. (3) Text search: Use query for free-text merchant search. (4) Special types: Use transaction_type for foreign, refunds, credits, duplicates, hsa_eligible, tagged. (5) Location-based: Use city or lat/lon with radius_km. (6) Tag filter: Use tag to find transactions with a specific tag. Rows are TERSE by default: transaction_id, date, amount, name, category_name, account_id, item_id, pending, excluded, internal_transfer. excluded answers "is this row excluded from spending?" — the raw per-transaction flag OR the row's category being user-excluded, the same union exclude_excluded filters on — so it is true for a row Copilot never stamped individually. It is therefore nearly always false in a default response, since exclude_excluded already removed those rows; it carries information only when you pass exclude_excluded: false. A cache row carries ~30 fields and most callers need a handful, so the rest are excluded unless asked for. PARTIAL list of what that drops, not exhaustive: Plaid metadata (plaid_category_id, plaid_category_strings, plaid_deleted), internal IDs (category_id, recurring_id, goal_id, parent_transaction_id, children_transaction_ids, user_id), enrichment and intelligence fields (normalized_merchant — still the normalized merchant name when requested — intelligence_suggested_category_ids, suggestion_ids, original_name), tag_ids, review state (user_reviewed, user_note, user_changed_type — true only when a human re-typed the row, so do not second-guess its type), location (city, region, country, lat/lon), and flags like is_amazon/from_investment. amazon is worth naming because nothing else hints it exists: on rows Copilot matched to an Amazon order it carries the receipt — {order_id, items: [{id, name, link, price, quantity}], other: {giftWrapping, rewards, savings, shipping, tax}} — so fields: ["default", "amazon"] answers "what was in this order" without an exported CSV. Ask for any of them by name with fields: ["default", "tag_ids"], or take the whole document with fields: ["all"] (or "*"). category_name is always resolved to a human-readable name. compact was removed in v3.0.0 — passing it now raises an error naming this migration.

NameRequiredDescriptionDefault
latNoLatitude for proximity search (use with lon and radius_km)
lonNoLongitude for proximity search (use with lat and radius_km)
tagNoFilter by tag name (e.g. "vacation")
cityNoFilter by city name (partial match)
limitNoMaximum number of results (default: 100)
queryNoFree-text search in merchant/transaction names
fieldsNoReturn only these fields per transaction. Default when omitted: a terse row (transaction_id, date, amount, name, category_name, account_id, item_id, pending, excluded, internal_transfer). Anything else is requestable by name alongside the preset: fields: ["default", "tag_ids"], or "all" / "*" for the whole row. THIS TOOL's own description names what its rows drop — the two modes drop different things, because a live row is not a cache document. The note is user_note in cache mode and user_notes live; either spelling works in either mode, and each mode returns its own. Unknown names are omitted and reported via _field_warning.
offsetNoNumber of results to skip for pagination (default: 0)
periodNoPeriod shorthand: this_month, last_month, last_7_days, last_30_days, last_90_days, ytd, this_year, last_year
regionNoFilter by region/city (case-insensitive substring)
countryNoFilter by country code (e.g., US, CL)
pendingNoFilter by pending status (true for pending only, false for settled only)
categoryNoFilter by category ID from get_categories (exact match, not a name). A parent category's id also returns its subcategories' rows. An id that belongs to no category and to no transaction the call looked at is an error.
end_dateNoEnd date (YYYY-MM-DD)
merchantNoFilter by merchant name (case-insensitive substring)
radius_kmNoSearch radius in kilometers (default: 10)
account_idNoFilter by account ID
max_amountNoMaximum transaction amount
min_amountNoMinimum transaction amount
start_dateNoStart date (YYYY-MM-DD)
transaction_idNoGet a single transaction by ID (ignores other filters, though a `category` value must still be a real id)
exclude_deletedNoExclude deleted transactions marked by Plaid (default: true)
exclude_excludedNoExclude user-excluded transactions (default: true)
transaction_typeNoFilter by special type. Cache-only: foreign (international) and duplicates (potential duplicate transactions, paired among the rows the other filters keep; pass the matching exclude_*: false, e.g. exclude_deleted: false, to bring a hidden copy back). Same rule in cache and live mode (positive amount = money out). refunds: a negative REGULAR row in a spending category; never INCOME, a transfer or uncategorized. credits: a negative REGULAR row whose name has credit/cashback/reward/rebate/bonus and whose category is not income or transfer; may overlap refunds. hsa_eligible: a heuristic, not an eligibility ruling: a positive REGULAR row whose category name has health/medical/pharmac/dental/eye care/vision or whose name has cvs/walgreens/pharmacy/medical/dental/vision/hospital. tagged: has a tag. type_specific_data totals cover all matching rows, not just the returned page.
exclude_transfersNoExclude transfers between accounts and credit card payments (default: true)
exclude_split_parentsNoExclude split-transaction parents (docs with children_transaction_ids). The children already carry the real categorized amounts — returning the parent would double-count the spend. Default: true.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Although annotations already declare `readOnlyHint=true`, the description adds substantial behavioral context: cache lag, default terse rows, the approximately 30 dropped fields, how `excluded` behaves, the special `amazon` receipt field, and that `compact` was removed and now raises an error. These are exactly the kind of beyond-annotation details an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the complexity and parameter count justify substantial detail. It is front-loaded with the cache-lag caveat and then organized by numbered usage modes, though some field-list prose could be trimmed without losing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 26-parameter read tool with no output schema, the description is complete: it covers cache behavior, alternate live tooling, supported retrieval modes, default return shape, field expansion, and a migration error for `compact`. An agent has enough information to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 26 parameters thoroughly. The description adds useful meaning beyond the schema by grouping parameters into retrieval modes, explaining the default terse field set, and clarifying the nuanced behavior of `excluded` and field selection. It is not a 5 because much of the parameter-level detail is already present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: reading transactions from a local LevelDB cache. It clearly distinguishes itself from the live sibling by naming `get_transactions_live` and explains that the cache may lag. An agent can tell this is the cached retrieval tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use this tool versus the real-time alternative: use the cache read normally, but use `get_transactions_live` for real-time data. It also enumerates the supported retrieval modes, giving clear context for selecting filters, transaction_id lookup, text search, type filters, location, and tags.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

refresh_databaseA
Read-only

Refresh the in-memory cache by reloading data from the local Copilot Money database. Use this when the user has recently synced new transactions in the Copilot Money app, or when you suspect the cached data is stale. The cache also auto-refreshes every 5 minutes. Returns the updated cache info after refresh.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses that it reloads from local database, returns cache info, and auto-refreshes. No contradiction with readOnlyHint=true. Adds value beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, no fluff, front-loaded with purpose, then usage, then additional context. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters and simple operation, description is complete. Mentions return of cache info, which is sufficient though slightly vague.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters; schema coverage 100%. Baseline score 4 for zero-param tool; description adds no parameter info but none needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Refresh the in-memory cache by reloading data from the local Copilot Money database'. Verb and resource specific, distinct from sibling get_* tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use: after user syncs new transactions or when cache may be stale. Notes auto-refresh every 5 minutes but lacks explicit exclusions or alternatives.

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.

  1. 5 tool updatesv3.0.0
    • Changedget_accounts1 field changed
      • addedInput schema / properties / fields
        Added value: +{
        +  "description": "Return only these fields per account row (e.g. [\"account_id\", \"name\", \"current_balance\"] for get_accounts, or [\"id\", \"name\", \"balance\"] for get_accounts_live — field names differ between the two modes). \"default\" expands to a terse baseline covering identity, name, type, balance, institution, and item — see each tool's own description for its exact field list and what \"default\" excludes. \"all\" or \"*\" returns the full row. Unknown names are omitted and reported via _field_warning.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
    • Changedget_holdings3 fields changed
      • changedInput schema / properties / account_id / description
        Previous value: -"Filter by investment account ID"New value: +"Filter by investment account ID. Returns empty for a hidden or merged account unless include_hidden is also set."
      • addedInput schema / properties / include_hidden
        Added value: +{
        +  "default": false,
        +  "description": "Include positions on hidden and merged accounts (default: false). Same flag, same default as get_accounts — leave it off and the two tools agree on which accounts exist.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / ticker_symbol / description
        Previous value: -"Filter by ticker symbol (e.g., \"AAPL\", \"SCHX\")"New value: +"Filter by ticker symbol, case-insensitive (e.g., \"AAPL\", \"SCHX\"). The cash sleeve is CUR:USD here and USD in live mode; either spelling matches it in both modes (CUR:USD matches only cash; a bare USD also matches a security tickered USD)."
    • Changedget_investment_prices3 fields changed
      • changedInput schema / properties / end_date / description
        Previous value: -"End date (YYYY-MM-DD or YYYY-MM)"New value: +"End date (YYYY-MM-DD)"
      • addedInput schema / properties / fields
        Added value: +{
        +  "description": "Return only these fields per row. Default when omitted: a terse row (security_id, ticker_symbol, price_type, date/month, latest_price, latest_at). The full intraday/daily series lives in `prices`, which is EXCLUDED by default because it is most of a row and multiplies response size several-fold — request it explicitly with fields: [\"default\", \"prices\"], or use \"all\" / \"*\" for full rows. Unknown names are omitted and reported via _field_warning.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedInput schema / properties / start_date / description
        Previous value: -"Start date (YYYY-MM-DD or YYYY-MM)"New value: +"Start date (YYYY-MM-DD)"
    • Changedget_recurring_transactions1 field changed
      • addedInput schema / properties / fields
        Added value: +{
        +  "description": "Return only these fields per detected recurring merchant. Default when omitted omits two expensive fields: `transactions` (the matched date/amount pairs, ~29% of a row) and `confidence_reason` (prose explaining the confidence score, ~15%). `confidence` itself is always in the default. Request the rest with fields: [\"default\", \"transactions\", \"confidence_reason\"], or \"all\" / \"*\" for full rows. Unknown names are omitted and reported via _field_warning.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
    • Changedget_transactions4 fields changed
      • changedInput schema / properties / category / description
        Previous value: -"Filter by category (case-insensitive substring)"New value: +"Filter by category ID from get_categories (exact match, not a name). A parent category's id also returns its subcategories' rows. An id that belongs to no category and to no transaction the call looked at is an error."
      • addedInput schema / properties / fields
        Added value: +{
        +  "description": "Return only these fields per transaction. Default when omitted: a terse row (transaction_id, date, amount, name, category_name, account_id, item_id, pending, excluded, internal_transfer). Anything else is requestable by name alongside the preset: fields: [\"default\", \"tag_ids\"], or \"all\" / \"*\" for the whole row. THIS TOOL's own description names what its rows drop — the two modes drop different things, because a live row is not a cache document. The note is user_note in cache mode and user_notes live; either spelling works in either mode, and each mode returns its own. Unknown names are omitted and reported via _field_warning.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedInput schema / properties / transaction_id / description
        Previous value: -"Get a single transaction by ID (ignores other filters)"New value: +"Get a single transaction by ID (ignores other filters, though a `category` value must still be a real id)"
      • changedInput schema / properties / transaction_type / description
        Previous value: -"Filter by special type: foreign (international), refunds, credits (cashback/rewards), duplicates (potential duplicate transactions), hsa_eligible (medical expenses), tagged (has tags)"New value: +"Filter by special type. Cache-only: foreign (international) and duplicates (potential duplicate transactions, paired among the rows the other filters keep; pass the matching exclude_*: false, e.g. exclude_deleted: false, to bring a hidden copy back). Same rule in cache and live mode (positive amount = money out). refunds: a negative REGULAR row in a spending category; never INCOME, a transfer or uncategorized. credits: a negative REGULAR row whose name has credit/cashback/reward/rebate/bonus and whose category is not income or transfer; may overlap refunds. hsa_eligible: a heuristic, not an eligibility ruling: a positive REGULAR row whose category name has health/medical/pharmac/dental/eye care/vision or whose name has cvs/walgreens/pharmacy/medical/dental/vision/hospital. tagged: has a tag. type_specific_data totals cover all matching rows, not just the returned page."
  2. 5 tool updatesv2.2.0
    • Removedget_investment_performance
    • Changedget_investment_splits5 fields changed
      • changedInput schema / properties / end_date / description
        Previous value: -"End date (YYYY-MM-DD)"New value: +"Optional. Inclusive upper bound on effective_date (YYYY-MM-DD)."
      • changedInput schema / properties / limit / description
        Previous value: -"Maximum number of results (default: 100, max: 10000)"New value: +"Maximum number of rows. Default 100, max 10000."
      • changedInput schema / properties / offset / description
        Previous value: -"Number of results to skip for pagination (default: 0)"New value: +"Pagination offset, default 0."
      • changedInput schema / properties / start_date / description
        Previous value: -"Start date (YYYY-MM-DD)"New value: +"Optional. Inclusive lower bound on effective_date (YYYY-MM-DD)."
      • changedInput schema / properties / ticker_symbol / description
        Previous value: -"Filter by ticker symbol (e.g., \"AAPL\", \"TSLA\")"New value: +"Optional. Case-insensitive ticker filter (e.g. \"NVDA\")."
    • Removedget_securities
    • Changedget_transactions1 field changed
      • addedInput schema / properties / exclude_split_parents
        Added value: +{
        +  "default": true,
        +  "description": "Exclude split-transaction parents (docs with children_transaction_ids). The children already carry the real categorized amounts — returning the parent would double-count the spend. Default: true.",
        +  "type": "boolean"
        +}
    • Removedget_twr_returns
  3. 17 tool updatesv2.0.1
    • First observedget_accounts
    • First observedget_balance_history
    • First observedget_budgets
    • First observedget_cache_info
    • First observedget_categories
    • First observedget_connection_status
    • First observedget_goal_history
    • First observedget_goals
    • First observedget_holdings
    • First observedget_investment_performance
    • First observedget_investment_prices
    • First observedget_investment_splits
    • First observedget_recurring_transactions
    • First observedget_securities
    • First observedget_transactions
    • First observedget_twr_returns
    • First observedrefresh_database

TDQS

A4.3/5.0

Scored across 14 tools

Disambiguation4/5

Each tool targets a distinct resource (transactions, accounts, holdings, budgets, goals, categories, splits, connection/cache status), and the descriptions steer callers correctly for the closest pairs (get_goals vs get_goal_history, get_accounts vs get_balance_history, get_cache_info vs get_connection_status). The main ambiguity comes from get_transactions being a six-mode mega-tool and from multiple tools referencing absent `_live` counterparts (get_transactions_live, get_investment_prices_live, get_investment_balance_live), which an agent cannot call.

Naming Consistency5/5

All tools use snake_case with a get_<resource> pattern, and the single exception (refresh_database) still follows the verb_noun convention. Resource names are predictable and parallel across domains (get_goals/get_goal_history, get_holdings/get_investment_prices).

Tool Count5/5

14 tools is well within the sweet spot for a personal-finance server covering transactions, accounts, investments, budgets, goals, and cache management. Each tool maps to a real domain object and none look redundant or padding.

Completeness4/5

Read coverage is thorough across every major domain: accounts, transactions (with search/filter/location/tag modes), balance history, budgets, categories, goals plus goal history, holdings, prices, splits, recurring charges, and cache/connection health. The gap is the absence of any mutation tools (edit transaction category, adjust budget/goal, create tags), so agents can analyze but not change anything.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    MCP server that provides AI assistants access to stock market data including financial statements, stock prices, and market news through a Model Context Protocol interface.
    11
    2,309
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    An MCP server implementation that provides programmatic access to personal finance data through LunchMoney's API, enabling AI assistants to manage transactions, budgets, categories, and assets.
    59
    308 npm
    110
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to manage Splitwise expenses with atomic duplicate prevention, smart fuzzy matching, and support for flexible split ratios between two people.
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to perform comprehensive Microsoft Excel operations including data analysis, cell editing, advanced formatting, and VBA execution on Windows systems. It provides a structured workflow for managing workbooks and worksheets through a dedicated Model Context Protocol interface.
    3
    5
    35 npm
    4
    MIT