Hundo
Server Details
Read your accounts, budgets and net worth, and draft changes you confirm.
- Status
- Healthy
- Uptime
- 99.8% over 21 days
- OAuth
- Works in Glama
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
TDQS
Scored across 21 tools
Tools have clearly distinct purposes: get_* for reads, propose_* plus confirm_proposal for mutations, and run_* for analytics. Overlaps like get_assets vs get_net_worth are clarified by descriptions (per-asset detail vs total wealth breakdown), and no two tools appear to do the same thing.
All 21 tools follow a consistent snake_case verb_noun pattern (get_, list_, propose_, confirm_, run_). The structure is predictable across every tool, with no deviations or mixed conventions.
21 tools is slightly above the typical 3-15 sweet spot, but each tool maps to a unique entity or action in a broad personal finance domain. The set is well-scoped, though a few propose_* variants could potentially be consolidated.
Core CRUD is covered for transactions and budgets (create/read/update/delete), and propose+confirm exists for assets, transfers, and saved insights. However, notable gaps remain: no tools to edit or delete transfers or asset trades, no IOU creation/management, no account or category management, and no unpin/delete for saved insights, which limits full lifecycle coverage.
Available Tools
21 toolsconfirm_proposalADestructiveInspect
Commit a pending proposal (created by a propose_* tool) to the user's finances. In this MCP context this is the ONLY way a proposal is recorded - there is no Hundo UI button. Call this with the proposalId returned by a propose_* tool (or from list_pending_proposals) once the user has approved the change in the conversation. Only call after the user has explicitly approved this exact proposal.
| Name | Required | Description | Default |
|---|---|---|---|
| proposalId | Yes | The proposal id to confirm. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and readOnlyHint=false, so the mutation risk is known; the description adds genuinely new context that this is the ONLY recording path (no UI fallback) and that a conversational user-approval gate must precede the call. It stops short of spelling out what the commit does to balances or whether it can be reversed, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the action, then the source of the id, then the approval gate — a sensible order with no filler. The approval requirement is restated in the final sentence, which is mildly redundant with the prior sentence but justified as a safety emphasis.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, id provenance, and the approval gate for a one-parameter destructive mutation, with annotations already carrying the safety profile and no output schema requiring return-value explanation. Only the concrete effect of committing (balance mutation, reversibility) is unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single proposalId parameter, so the schema baseline is 3. The description adds provenance value the schema lacks by naming two concrete sources for the id (a propose_* return value or list_pending_proposals).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Commit a pending proposal ... to the user's finances') and immediately scopes the resource's origin ('created by a propose_* tool'). This cleanly separates it from the sibling propose_* tools that create proposals rather than commit them, and from list_pending_proposals which only reads them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit prerequisites and ordering: call it with the proposalId returned by a propose_* tool or from list_pending_proposals, and only after the user has explicitly approved that exact proposal in the conversation. The gating condition is stated twice with emphasis, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_accountsARead-onlyInspect
List the user's financial accounts. Use this to resolve an account name mentioned in an email (e.g., 'BCA ***1234') to an accountId before calling propose- tools. If no confident match, omit accountId on the proposal - the user will pick on review.
| Name | Required | Description | Default |
|---|---|---|---|
| nameLike | No | Optional case-insensitive substring filter on account name (e.g., 'bca', 'robinhood', 'visa'). Omit to list all accounts. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already denote this as read-only, so the safety profile is covered. The description adds useful behavioral context beyond annotations: the account resolution purpose, the fallback behavior when there is no confident match, and that the user will make the final selection on review.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded. The first sentence states the core purpose, the second explains the workflow context, and the third gives a concrete conditional behavior. There is no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter, read-only tool, the description is complete enough for correct invocation. It explains the resolution workflow and the no-match behavior. It could optionally mention the output fields, but the accountId reference implies the key return value.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the only parameter nameLike is already described with examples and a clear omission behavior. The tool description does not add parameter-level meaning beyond the schema, which matches the baseline for full coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'List the user's financial accounts.' It further clarifies its role by connecting it to resolving account names to accountIds before propose-* tools, which distinguishes it from the other get_* siblings and the propose/trade tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use the tool: 'Use this to resolve an account name mentioned in an email ... before calling propose-* tools.' It also gives a clear decision rule for low-confidence matches, instructing the agent to omit accountId. It does not directly mention alternatives or negative conditions, but the workflow guidance is strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_assetsARead-onlyInspect
List the user's assets (crypto, stocks, real estate, vehicles, trading cards, etc.) with quantity, purchase price, latest market valuation, and P&L - the same numbers the app's net-worth page shows. costBasis, pnl, realizedPnl, and avgCostPerUnit are in the user's base currency; costBasis is the blended total cost (per-trade history included) and avgCostPerUnit is costBasis/quantity - use avgCostPerUnit for any 'average cost per share' question, NOT purchasePrice (which is only the fallback for units without recorded trades). purchasePrice and latestValuation.value are per-unit in the asset's own currency. All monetary fields are in major units. pnl is null only when no market price/valuation or exchange rate is available for the asset. Name an asset by displaySymbol when it has one; for a trading card (type 'collectible') symbol is an internal key, never a name to show.
| Name | Required | Description | Default |
|---|---|---|---|
| type | No | Filter by asset type. Omit to return all. 'collectible' is the user's trading cards. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the readOnlyHint annotation, disclosing detailed data semantics: currency distinctions (base vs. per-unit), cost basis calculation methods, fallback behavior for purchasePrice, null conditions for pnl, and naming conventions for displaySymbol vs. internal keys. This rich behavioral disclosure prepares the agent for accurate interpretation of results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is lengthy but every sentence adds substantive value, covering purpose, field meanings, currency handling, null behavior, and naming. It is front-loaded with the primary action, then systematically details nuances. While dense, it avoids redundancy and is well structured for its complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by specifying key return fields and their semantics, including currency, null cases, and naming. It does not mention pagination, ordering, or limits, but for a tool that lists assets these are minor gaps. The description is comprehensive enough for correct invocation and interpretation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter 'type' is fully documented in the schema with an enum and description, so the schema already provides complete parameter semantics. The tool description does not add any new information about this parameter beyond what the schema states, meeting the baseline for 100% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('List'), the resource ('the user's assets'), and enumerates the asset types (crypto, stocks, real estate, etc.) plus the returned fields (quantity, purchase price, latest market valuation, P&L). It also ties it to the app's net-worth page, making the tool's role unambiguous and distinct from siblings like get_net_worth.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context by referencing the net-worth page and detailing asset-level data, but it does not explicitly state when to use this tool versus alternatives such as get_net_worth or get_accounts. No exclusions or conditions are given, so an agent must infer the appropriate scenario.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_budgetARead-onlyInspect
Get the user's budgets for a specific month, with budgeted/spent/remaining for each. Use for questions about budget status, overspending, or remaining budget balances. Month must be 'YYYY-MM'.
| Name | Required | Description | Default |
|---|---|---|---|
| month | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds real value beyond them by disclosing the return shape (budgeted/spent/remaining per budget) and enforcing a month format, which matters since no output schema exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each load-bearing: capability and payload, then routing guidance, then the parameter constraint. Front-loaded and free of filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with no output schema, the description covers purpose, when to use it, the argument format, and the returned fields. Only minor gaps remain, such as what happens when no budgets exist for a month.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must carry the parameter meaning, and it does: 'Month must be YYYY-MM.' It clarifies the single required parameter's accepted format beyond the raw regex pattern in the schema. It stops short of noting behavior for invalid or omitted months.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Get the user's budgets') and further specifies the scope ('for a specific month') and payload ('budgeted/spent/remaining for each'). This clearly distinguishes it from the write-oriented siblings like propose_budget_upsert and propose_delete_budget without needing the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit usage context: 'Use for questions about budget status, overspending, or remaining budget balances.' It does not name an alternative tool or state exclusions, but the read/write split against the propose_* siblings is unambiguous from the stated intent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_categoriesARead-onlyInspect
List the user's transaction categories. Use this to resolve a category mentioned by the user to a categoryId before proposing a transaction or asset trade. If there is no confident match, omit categoryId. If the result is truncated, call again with a more specific nameLike.
| Name | Required | Description | Default |
|---|---|---|---|
| nameLike | No | Optional case-insensitive substring filter on category name. Omit to list all categories. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds genuinely new behavioral context beyond the annotations: results can be truncated and how to react, plus the no-confident-match fallback of omitting categoryId. It does not describe the return shape, but the truncation and matching semantics are the important behavioral facts here.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each front-loaded and load-bearing: purpose, then use-case plus fallback, then truncation handling. No filler or restated structured data.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-optional-param read tool with annotations covering safety and no output schema, the description covers purpose, resolution workflow, and both edge cases (no match, truncation). Nothing material to correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already defines nameLike as a case-insensitive substring filter. The description reinforces this indirectly ('more specific nameLike') when discussing retries but adds no format, syntax, or semantic detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the user's transaction categories') and no sibling tool returns categories, so it is unambiguous against get_accounts, get_assets, etc. The description also frames the resource's purpose (categoryId resolution), which sharpens what the list is for.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the trigger ('before proposing a transaction or asset trade') and the direct consumers (propose_transaction, propose_asset_trade). It further covers two edge cases: omitting categoryId on no confident match, and re-calling with a narrower nameLike on truncation, which is exactly the when/when-not 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_iousARead-onlyInspect
Get the user's IOU/debt ledger: who owes the user and whom the user owes, across all counterparties (or one, via counterpartyId). Each entry is a single transfer against a per-counterparty virtual account, labeled with a direction - 'lend' (user gave money, receivable grows), 'collect' (user was paid back, receivable shrinks), 'borrow' (user received money, payable grows), or 'settle' (user paid back, payable shrinks). virtualAccount.type is 'receivable' (they owe the user) or 'payable' (the user owes them); virtualAccount.currency is the debt's currency. amount is in major units. Use this for any question about who owes whom, outstanding debts, or IOU history - this data is not visible via run_read_query.
| Name | Required | Description | Default |
|---|---|---|---|
| counterpartyId | No | Filter to a single counterparty by id (resolve a name to an id via run_read_query against the counterparty table first). Omit to return the ledger for every counterparty. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even though readOnlyHint=true is already in annotations, the description adds meaningful behavioral detail beyond that: it defines the four direction labels and their effect on receivable/payable balances, explains virtualAccount.type and currency, and notes that amount is in major units. This goes well beyond a mere read-only declaration.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but not bloated; the core purpose and scope are front-loaded in the first sentence. The later sentences explain transfer semantics, virtual account fields, and units, all of which are necessary for correct interpretation. Slightly verbose, but every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has one optional parameterchers and no output schema, yet the description fully covers what the ledger contains, the meaning of each entry direction, the virtualAccount fields, units, and the filtering option. It also addresses the primary alternative. Nothing essential is missing for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the counterpartyId parameter is already described with filtering semantics and how to resolve names via run_read_query. The description only briefly reinforces "or one, via counterpartyId," adding little beyond the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description begins with a specific verb and resource: "Get the user's IOU/debt ledger," then defines the scope as who owes the user and whom the user owes. It also differentiates itself from run_read_query by explicitly stating this data is not visible there, so an agent can distinguish it from the closest sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit usage guidance: "Use this for any question about who owes whom, outstanding debts, or IOU history." It also names the alternative and explains the boundary: "this data is not visible via run_read_query." This gives the agent clear when-to-use and when-not-to-use context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_net_worthARead-onlyInspect
Get the user's current net worth in their base currency, with a breakdown by account and asset. Each asset also carries costBasis, pnl (both in the base currency, major units), and pnlPercent - the same numbers the net-worth page shows. Use this for any question about total wealth, total assets, total liabilities, or how rich/poor the user is.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds real behavioral context: values are in the user's base currency in major units, costBasis/pnl are also base-currency, and the figures are stated to match the net-worth page. It does not mention staleness or refresh timing, which would be the remaining useful detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each doing distinct work: what is returned, the field-level detail with currency semantics, and when to reach for it. The core purpose is front-loaded and nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no parameters and no output schema, the description carries the full burden of describing the return, and it does: currency basis, per-account and per-asset breakdown, and the specific derived fields. An agent can call this and interpret the result without further documentation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters and the schema is an empty object, so there is nothing for the description to disambiguate. Per the baseline for parameterless tools, a 4 is appropriate; no parameter meaning is missing.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('get the user's current net worth') plus scope: base currency, broken down by account and asset. It also enumerates the returned fields (costBasis, pnl, pnlPercent), which lets an agent distinguish it from siblings like get_accounts and get_assets that return granular primitives instead of a rolled-up total.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The final sentence explicitly routes usage: 'Use this for any question about total wealth, total assets, total liabilities, or how rich/poor the user is.' That is clear context, but it names no alternative or exclusion (e.g., when to prefer get_accounts or get_assets for per-item detail), so it stops short of full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_saved_insightsARead-onlyInspect
List the user's pinned dashboard insights (Saved Insights tab): id, title, viz spec, and sort order. Does not recompute current data for each one - pass an insight's spec to run_insight_query if you need fresh numbers. Use this to see what's already pinned before proposing a new one, or to find an insight's id/spec to edit or rerun.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Optional saved insight id to fetch a single pinned insight. Omit to list all of the user's pinned dashboard insights. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds a genuinely useful non-obvious trait: it does not recompute current data, pointing the agent to run_insight_query for fresh numbers. It stops short of describing pagination or empty-list behavior, so not a full 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, each earning its place: what it returns, the staleness caveat with an escape hatch, and the usage trigger. Front-loaded with the core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description usefully enumerates the returned fields. Combined with the annotations and a fully documented param, an agent has everything needed to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single optional id parameter is fully documented in the schema, so the baseline is 3. The description adds return-field context but no additional meaning about how id behaves.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the user's pinned dashboard insights') and even enumerates the returned fields (id, title, viz spec, sort order). It clearly distinguishes itself from propose_saved_insight and run_insight_query, both named in the text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent when to use it: 'to see what's already pinned before proposing a new one, or to find an insight's id/spec to edit or rerun.' It also routes the fresh-data case to run_insight_query, giving a named alternative with its selection condition.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_yesterday_summaryARead-onlyInspect
Get yesterday's morning briefing: net-worth change, budget alerts (envelopes at >= 80% utilization), and yesterday's transaction/transfer/asset activity, plus a single prioritized headline (insight) summarizing what matters most. All monetary values are in major units. Use this for 'what happened yesterday' / daily-digest style questions instead of composing it from run_read_query - the alert thresholds and headline priority are not simple SQL.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses key behavioral traits beyond annotations: read-only nature is covered by readOnlyHint, but the description adds that all monetary values are in major units and that the tool computes nontrivial logic (alert thresholds, headline priority). It does not describe pagination or return format, but with no output schema and readOnlyHint covering safety, this is a minor gap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single long sentence but is front-loaded with the core purpose and then adds detail and usage guidance. It is dense but every clause earns its place; no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a no-param, read-only tool with no output schema, the description provides enough context: what it returns, how it differs from run_read_query, and the monetary unit convention. It doesn't explain the return structure, but given the absence of an output schema, this is acceptable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so baseline is 4. The description adds no parameter details because there are none to add, and it correctly avoids inventing any.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (get) and resource (yesterday's morning briefing) and enumerates the exact contents: net-worth change, budget alerts at >=80%, transaction/transfer/asset activity, and a prioritized headline. This is far more specific than sibling tools like get_net_worth or run_read_query, making the scope unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent when to use this tool: 'what happened yesterday' / daily-digest style questions. It also names the alternative (composing from run_read_query) and explains why this tool is preferable - the alert thresholds and headline priority are not simple SQL.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_pending_proposalsARead-onlyInspect
List your currently-pending proposals (proposals you've made that the user hasn't confirmed or cancelled yet). Use this when the user asks to change one of several pending proposals so you only re-propose the affected one. Returns up to 20 most-recent pending proposals. Amounts in summaries are in MAJOR units - pass them through to the propose tools as-is for relative edits.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already signal readOnlyHint, and the description adds non-redundant behavior: returns at most 20 most-recent pending proposals and explains that amounts are in MAJOR units and should be passed through as-is for relative edits. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences front-load the core function, then give usage context, then provide result and unit constraints. Every sentence adds necessary information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read-only tool with no output schema, this is complete: it specifies return scope, recency ordering, the 20-item cap, and the unit semantics needed for downstream propose tools. All required invocation knowledge is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has no parameters (schema properties {}), so there is no parameter burden for the description to carry. Schema coverage is 100% by construction, and the description appropriately omits parameter explanation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Uses a specific action verb and resource: 'List your currently-pending proposals' and defines what pending means (made but not confirmed/cancelled). This makes it easy to distinguish from the propose_* and confirm_proposal siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger: use when the user asks to change one of several pending proposals, so only the affected one is re-proposed. It does not name a when-not-to-use alternative, but the stated scenario is sufficient for a zero-parameter read-only list tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_asset_convertAInspect
Propose converting one asset into another (a cashless swap, e.g. spend SOL to acquire a new coin). Records a sell of the source and a buy of the destination at one shared value, moving no cash account. When the user does not hold the destination yet, pass newDestAsset instead of destAssetId: confirming creates it. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). Validates the user holds enough of the source. Pass the trade value in the user's base currency, or omit it to auto-derive from the source's market price. Always include a short user-supplied description.
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | YYYY-MM-DD. | |
| value | No | Trade value in MAJOR units of the user's base currency (the displayed amount, e.g. 850 for $850). This is the value of the source quantity at trade time; it becomes the destination's cost basis. Omit to auto-derive it from the source asset's latest market price. | |
| description | Yes | Short user-supplied description, e.g. 'Swapped SOL for BONK'. | |
| destAssetId | No | Id of the asset being acquired (the destination), from getAssets. Must differ from the source. Omit it only when the user does not hold the destination yet, and pass newDestAsset instead. | |
| destQuantity | Yes | Number of units of the destination asset received. | |
| newDestAsset | No | Pass this INSTEAD of an asset id, only when getAssets shows the user does not hold the asset yet. Confirming the proposal creates the asset and records the trade together. | |
| sourceAssetId | Yes | Id of the asset being spent (the source of the swap). | |
| sourceQuantity | Yes | Number of units of the source asset to spend. | |
| editsProposalId | No | Id of an existing pending proposal to update in place. Pass this when the user asks to modify a proposal you previously made (e.g. 'make it 5 SOL instead'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations covering the safety profile (readOnlyHint=false, destructiveHint=false), the description adds substantial behavioral context: it creates only a pending proposal, nothing is recorded until confirm_proposal, the source balance is validated, and confirming newDestAsset also creates the asset. The one gap is that idempotentHint=false implies repeated calls spawn duplicate proposals, which is never warned about.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core operation, then flows into conditional guidance and the next step. Mostly tight, but the final MCP note largely restates the earlier 'only creates a pending proposal' sentence, so there is mild redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter mutation tool with nested objects and no output schema, the description still covers the proposal-only lifecycle, the confirm step, the returned proposalId, required inputs, and conditional parameter routing. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so the baseline is 3, but the description adds genuine routing semantics beyond the schema: the newDestAsset-vs-destAssetId choice and the editsProposalId modification path, plus the value auto-derivation behavior and its role as the destination's cost basis.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('propose converting one asset into another') and immediately qualifies it as a cashless swap that records a sell and a buy at one shared value with no cash movement. This distinguishes it cleanly from siblings like propose_asset_trade and propose_transfer without needing the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing rules are given for every ambiguous case: use newDestAsset instead of destAssetId when the user doesn't hold the destination, pass editsProposalId to modify an existing proposal, omit value to auto-derive from market price, and always supply a description. It also names the required follow-up (confirm_proposal) and warns there is no UI affordance in this context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_asset_tradeAInspect
Propose a buy or sell trade for an asset. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). To buy an asset the user does not hold yet, pass newAsset instead of assetId: confirming creates the asset and records the buy together. For sells, validates the user holds enough quantity. The cash account currency must match priceCurrency. Always include a short user-supplied description; optionally include a categoryId.
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | YYYY-MM-DD. | |
| fees | No | Transaction fees in MAJOR units (e.g. 1.99 for $1.99, 5000 for Rp 5,000). Pass 0 if none. | |
| action | Yes | ||
| assetId | No | Id of the asset being traded, from getAssets. Omit it only for a buy of an asset the user does not hold yet, and pass newAsset instead. | |
| newAsset | No | Pass this INSTEAD of an asset id, only when getAssets shows the user does not hold the asset yet. Confirming the proposal creates the asset and records the trade together. | |
| quantity | Yes | Number of units to buy or sell. | |
| categoryId | No | Optional category id to attach to the cash transaction. | |
| description | Yes | Short user-supplied description, e.g. 'DCA into AAPL'. | |
| pricePerUnit | Yes | Per-unit price in MAJOR units (the displayed amount, e.g. 180.25 for $180.25, 3123.98 for Rp 3,123.98). Fractional values are allowed (e.g. 0.000001 for high-supply crypto). The server scales to storage units; never pre-multiply by 100. | |
| cashAccountId | No | Id of the cash account funding or receiving this trade. Omit only in the email-import pipeline when the source email doesn't make it clear which cash account is involved - the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead. | |
| priceCurrency | Yes | 3-letter currency code for the trade price. | |
| editsProposalId | No | Id of an existing pending proposal to update in place. Pass this when the user asks to modify a proposal you previously made (e.g. 'make it $50 instead'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as a non-readOnly, non-idempotent write with destructiveHint=false, but the description adds substantial behavioral context: nothing is recorded until confirm_proposal runs, sells are validated against held quantity, confirming a newAsset buy also creates the asset, and the cash account currency must match priceCurrency. These are cross-field and lifecycle traits annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then the confirm step, then parameter branching, then constraints – a logical order with no filler sentences. It is dense and runs slightly long for a single paragraph, but every sentence carries information the agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter mutation with no output schema, the description covers the two-phase lifecycle, the new-asset branch, and a key cross-field constraint, and it names the single return value (proposalId). It stops short of mentioning the related listPendingProposals lookup or how a created proposal is later surfaced.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 92%, so the baseline of 3 applies, but the description adds genuine cross-parameter meaning beyond the schema: the assetId-versus-newAsset branch rule, the priceCurrency/cash-account currency constraint, and the requirement that description be user-supplied. It does not, however, explain editsProposalId, fees, or categoryId workflow.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Propose') and resource ('a buy or sell trade for an asset') and immediately distinguishes itself from confirm_proposal by declaring that it only creates a pending proposal. An agent can tell it apart from siblings like propose_transaction or propose_asset_convert without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing conditions: call confirm_proposal with the returned proposalId after user approval, pass newAsset instead of assetId when the user does not hold the asset, and use assetId otherwise. It also warns that there is no UI button or proposal card, which is a real usage constraint an agent would otherwise have to infer.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_budget_upsertAInspect
Propose creating a new budget or updating an existing one. For create: provide name, allocated, currency. For update: provide envelopeId, allocated; the name and currency are kept from the existing budget. A budget resets weekly or monthly (period, default monthly) and tracks spending one of two ways: trackingMode 'category' counts the linked categories, and ONE budget can cover MANY: pass every category in categoryIds (e.g. rent + groceries + transport + utilities for a single 'Needs' budget); trackingMode 'manual' links no categories and the user tracks it by hand. On update, categoryIds replaces the current list, so pass the whole list. Look category ids up first and never invent them. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context).
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Required when creating a new budget. | |
| period | No | How often the allocation resets. Defaults to 'monthly' on create; left unchanged on update unless you pass it. | |
| currency | Yes | ||
| allocated | Yes | Allocation in MAJOR units (the displayed amount, e.g. 500.00 for $500.00, 5000000 for Rp 5,000,000). The server scales to storage units; never pre-multiply by 100. | |
| categoryId | No | Shorthand for a categoryIds list of one. It is NOT an 'add this one' field: on update it replaces the budget's whole category list, exactly as categoryIds does, so a budget that already covers several would be left with just this one. To add a category to an existing budget, read its current list and pass all of them in categoryIds. | |
| envelopeId | No | Id of an existing budget to update; omit to create a new budget. | |
| categoryIds | No | Every category this budget tracks, e.g. rent + groceries + transport + utilities for one 'Needs' budget. On update this REPLACES the budget's current categories, so pass the full list you want, not just the new ones. Look ids up first; never invent them. | |
| trackingMode | No | 'category' counts spending from the linked categories automatically; 'manual' counts nothing and the user records progress by hand. Omit on create and it follows whether you passed categories. | |
| editsProposalId | No | Id of an existing pending proposal to update in place. Pass this when the user asks to modify a proposal you previously made (e.g. 'make it $50 instead'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. | |
| includeInvestments | No | Count asset purchases (buys) toward this budget's spending alongside expenses. Defaults to false on create; left unchanged on update unless you pass it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses that the tool only creates a pending proposal until confirm_proposal is called, that categoryIds replaces the whole category list on update, that name/currency are preserved on update, and that a proposal id is returned. These are exactly the non-obvious behavioral 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and information-rich, and it is front-loaded with the core create/update distinction. Almost every sentence earns its place, though the closing MCP note partly repeats the earlier confirm_proposal guidance, adding minor redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex 10-parameter tool with no output schema and all-false annotations, the description covers the full lifecycle: how to propose, what each mode means, how replacement semantics work, how to reuse proposals, and how to finalize via confirm_proposal. An agent has enough context to call this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even though schema coverage is high, the description adds essential semantics: which parameters apply on create vs update, that categoryId is a shorthand replacement rather than an additive field, that allocated is in major units and must not be pre-multiplied, and that trackingMode 'category' counts linked categories while 'manual' tracks by hand. This goes well beyond the field-level schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: proposing creation or update of a budget. It clearly distinguishes the create and update flows with the fields each requires, and it is unambiguous next to siblings like confirm_proposal or propose_delete_budget.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit conditions for create vs update, explains when to pass envelopeId, categoryIds, and editsProposalId, and directs the agent to listPendingProposals when unsure. It also clearly states that confirm_proposal must be called afterward and that there is no UI button in this context, leaving no ambiguity about when to use this tool vs its downstream sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_delete_budgetAInspect
Propose deleting an existing budget (envelope). Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). Look the budget up with the budget tool first and pass its id. Transactions already assigned to the budget are kept - only the budget itself is removed.
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| envelopeId | Yes | Id of the budget (envelope) to delete. Look it up with the budget tool before calling. | |
| editsProposalId | No | Id of an existing pending budget-delete proposal to update in place. Pass this when the user retargets a delete you previously proposed. Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate non-destructive behavior (destructiveHint=false), but the description adds crucial context: the tool only creates a pending proposal and nothing is recorded until confirm_proposal is called. It also discloses that existing transactions are kept and only the budget is removed, clarifying side effects. This significantly enhances the agent's understanding beyond what annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and front-loaded with the core purpose, followed by essential workflow details in a few sentences. Every sentence contributes value, with no redundancy or irrelevant information. Structure is logical: purpose → return value → next step → lookup instruction → side-effect note → proposal-only caveat.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's moderate complexity (2 params, no output schema), the description covers all necessary information: what it does, what it returns, how to use it, what happens to associated transactions, and the critical 'nothing is recorded until confirm_proposal' caveat. An agent can successfully invoke this tool without additional external context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with descriptive parameter comments, so baseline is 3. The description adds workflow context: 'Look it up with the budget tool first' for envelopeId and explains the purpose of editsProposalId in the context of retargeting a previous proposal. This adds meaningful guidance beyond the schema, justifying a score above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with the specific verb+resource combination 'Propose deleting an existing budget (envelope)' and immediately clarifies the proposal nature, distinguishing it from actual deletion. It also names the follow-up tool (confirm_proposal) and the lookup tool (budget), making the purpose unmistakable and differentiating it from sibling propose tools like propose_budget_upsert or propose_delete_transaction.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly instructs the agent to look up the budget first with the 'budget tool' and pass its id, and states that there is no UI button or proposal card in this context, so the agent must use confirm_proposal to record. It also provides guidance on using editsProposalId for updating an existing pending proposal, including how to look up current ids via listPendingProposals. This is clear when-to-use and how-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_delete_transactionAInspect
Propose deleting an existing income or expense transaction. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). Look the target up with the read-query tool first and pass its id. Transfers, buys and sells can't be deleted here - tell the user to delete those on the Transactions page. Neither can IOUs, which are transfers to a virtual receivable/payable account - tell the user to manage those on the IOUs page. The preview states the full effect - relay it to the user before they confirm.
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| transactionId | Yes | Id of the transaction to delete. Look it up with the read-query tool (filtering by description/date/amount/etc.) before calling. Must be an income or expense: transfers, buys and sells belong on the Transactions page, and IOUs on the IOUs page. | |
| editsProposalId | No | Id of an existing pending delete proposal to update in place. Pass this when the user retargets a delete you previously proposed (e.g. 'actually delete the other one'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description clearly discloses that the tool only CREATES a pending proposal and nothing is recorded until confirm_proposal is called, which is crucial behavioral context beyond the annotations (which only mark it non-read-only and non-destructive). It also mentions the preview and instructs relaying it to the user, adding valuable behavioral detail without contradicting annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but every sentence serves a purpose: main action, workflow, exclusions, parameter guidance, and the MCP note. It is front-loaded with the primary purpose and uses structured paragraphs, though the MCP note partially repeats earlier content. A slightly tighter wording would earn a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with two parameters, no output schema, and a non-trivial propose-then-confirm workflow, the description leaves nothing essential out. It covers prerequisites (lookup with read-query), post-steps (confirm_proposal), boundary conditions (which transaction types are excluded), and user interaction (relay preview). An agent can invoke it correctly without additional information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents both parameters. The description adds meaningful context by explaining how to obtain transactionId (via read-query, filtering) and its constraints, and by illustrating when to use editsProposalId. This goes beyond the schema's structural descriptions, though not to the level of exhaustive semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Propose deleting an existing income or expense transaction.' It also differentiates from siblings by explicitly stating which transaction types are out of scope (transfers, buys, sells, IOUs) and directs the user to the appropriate pages, so an agent can distinguish it from other propose_* tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: look up the target with the read-query tool first, pass its id, and call confirm_proposal with the returned proposalId after user approval. It also tells when not to use the tool (for transfers, buys, sells, IOUs) and provides the editsProposalId scenario for updating a pending proposal, leaving little to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_saved_insightAInspect
Propose pinning a filtered insight to the user's Saved Insights tab. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context).
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | The visualization spec. vizType is one of: 'value' (single number), 'comparison' (template income_vs_expense / period_over_period, or custom A/B), 'chart_time' (bucketed time series), 'breakdown' (top groups by category/envelope/account/type), 'table' (list of N transactions). Always set the filter.period - use relative.value='this_month' / 'last_30_days' etc. unless the user gave explicit dates. | |
| title | Yes | Short user-facing label for the pinned insight, e.g. 'Expenses this month excluding Investment' or 'Dating spending over 6 months'. | |
| editsProposalId | No | Id of an existing pending saved-insight proposal to update in place. Pass this when the user asks to adjust a proposal you previously made (e.g. 'exclude Dining too'). Use listPendingProposals if unsure. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With all annotations false, the description carries the full behavioral burden, and it delivers: it discloses the two-phase side-effect boundary ('only CREATES a pending proposal; nothing is recorded until confirm_proposal is called'), the return value (proposal id), and the no-UI context that forces the agent to complete the confirmation itself. This is exactly the critical behavioral context that annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: purpose, then the confirm_proposal workflow with the critical no-UI caveat, then a deliberately emphasized MCP note reinforcing that nothing is recorded. The slight redundancy of the final note is justified given the high cost of an agent assuming the insight was saved after only this call.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite the complex spec schema and no output schema, the combination is complete: the schema's parameter descriptions handle spec construction, the description handles the proposal lifecycle, the return shape (proposalId) is disclosed, and the follow-up tool is named. An agent has everything needed to call it correctly and know what happens next.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline of 3 applies even though the tool description adds no parameter-level detail. The schema's own descriptions are genuinely strong — the spec description enumerates all five vizTypes with guidance to always set filter.period, the title gives concrete examples, and editsProposalId explains update-in-place semantics and routes to listPendingProposals — but the description text itself contributes nothing extra for parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action verb ('Propose pinning'), a clear resource ('filtered insight to the user's Saved Insights tab'), and immediately distinguishes the tool from its sibling confirm_proposal by framing it as the pending-proposal half of a two-phase flow. It is unambiguous against the other propose_* siblings and against get_saved_insights, which is a separate read operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit workflow guidance: call this to get a proposalId, then call confirm_proposal after the user approves, with a note that no UI button exists to click in this context. It does not explicitly state when-not-to-use alternatives (e.g., that queries should go to run_insight_query), but the propose-vs-confirm lifecycle is so clearly routed that the essential usage decision needs no inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_transactionAInspect
Propose a new income or expense transaction. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). Validate account exists and is owned by the user before calling.
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | YYYY-MM-DD. | |
| type | Yes | ||
| amount | Yes | Amount in MAJOR units (the displayed amount, e.g. 12.34 for $12.34, 50000 for Rp 50,000). The server scales to storage units; never pre-multiply by 100. | |
| currency | Yes | 3-letter currency code. May differ from the account's currency; the server records an exchange rate to convert into the account currency. | |
| accountId | No | Id of the financial account. Omit only in the email-import pipeline when the source email is genuinely ambiguous about which account paid - the user will pick one via the proposal card's Edit button. In chat, always pass a real id (ask the user instead of guessing). Do not pass an empty string; omit the field entirely instead. | |
| categoryId | No | ||
| envelopeId | No | ||
| description | Yes | ||
| exchangeRate | No | Rate to convert the amount into the account's currency, used only when currency differs from the account. Usually omit - the server auto-fetches the rate as of the date. | |
| editsProposalId | No | Id of an existing pending proposal to update in place. Pass this when the user asks to modify a proposal you previously made (e.g. 'make it $50 instead'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=false, idempotentHint=false), the description discloses the non-obvious two-phase behavior: a proposal is only staged and no record is created until confirm_proposal runs. It also tells the agent not to wait for a UI click in this context. It does not detail duplicate-proposal side effects, but the highest-risk behavioral trait is exposed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The text is front-loaded with the tool's purpose and gives the workflow in the first paragraph. The MCP note partly repeats the 'pending, not recorded' point from the first paragraph, so one sentence is redundant. Overall, the structure is efficient for the amount of guidance it carries.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the return value, the follow-up call, the account precondition, and the no-UI caveat, which is substantial for a tool with no output schema. It does not explicitly document categoryId/envelopeId or distinguish this from propose_transaction_edit for editing confirmed transactions. There is also minor ambiguity between 'no proposal card in this context' and the schema's email-pipeline proposal card, but the core invocation path is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description supplies one piece of parameter semantics: validate that accountId exists and is owned by the user before calling. Beyond that, it adds no parameter-level detail; amount units, currency conversion, editsProposalId, and accountId omission rules are all left to the schema. With coverage around 60%, the undocumented categoryId and envelopeId have no compensating prose, so the dimension is adequate but not strong.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the specific action and resource ('Propose a new income or expense transaction') and immediately adds the two-phase workflow, so it is not confusable with confirm_proposal. The word 'new' plus the editsProposalId guidance separates it from proposal editing, and the pending-vs-recorded distinction is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly frames confirm_proposal as the required next step after user approval, instructs the agent to validate account ownership before calling, and distinguishes new proposals from edits via editsProposalId. It gives clear when-not guidance for accountId in the schema (omit only in ambiguous email import; ask in chat). It does not explicitly rule out propose_transaction_edit for recorded transactions, so the guidance is clear but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_transaction_editAInspect
Propose an edit to an existing past income or expense transaction. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context). Only works on income/expense transactions - transfers and asset trades (buy/sell) must be edited on the Transactions page. ONLY pass the fields the user explicitly asked to change; omit the rest so they keep their current value. Pass null for an optional field only when the user explicitly asks to clear that field (e.g. 'remove the category').
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | Optional. New date (YYYY-MM-DD). | |
| type | No | Optional. New type for the transaction. Only income↔expense is allowed via this tool. | |
| notes | No | Optional. New notes, or null to clear. | |
| amount | No | Optional. New amount in MAJOR units (displayed amount, e.g. 12.34 for $12.34). Never pre-multiply by 100. | |
| currency | No | Optional. New 3-letter currency code. Must match the transaction's account currency (no foreign-currency edits via chat). | |
| categoryId | No | Optional. New category id, or null to clear the category. | |
| envelopeId | No | Optional. New budget (envelope) id, or null to clear it. | |
| description | No | Optional. New description. Pass null to clear it. | |
| transactionId | Yes | Id of the existing transaction to edit. Look it up with runReadQuery (filtering by description/date/amount/etc.) before calling. | |
| editsProposalId | No | Id of an existing pending edit-proposal to update in place. Pass this when the user revises a previous edit you proposed ('actually make it $60 not $50'). Omit when proposing a fresh edit. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only indicate non-readonly and non-destructive, but the description reveals the crucial behavioral trait: this tool only creates a pending proposal and nothing is recorded until confirm_proposal is called. It also discloses that there is no UI button or proposal card in this context, which explains an otherwise surprising workflow. This goes well beyond what the structured annotations convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but not bloated; every major rule earns its place, and the critical pending-proposal caveat is repeated at the end as an MCP note, which is mildly redundant. The structure is well organized: purpose first, then workflow, exclusions, field semantics, and the final warning. It is appropriately front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter tool with a nontrivial partial-update and confirmation workflow, the description covers everything needed to call it correctly: the pending nature, the required follow-up, the types of transactions allowed, the field-passing rules, and the return value. With no output schema present, saying it returns a proposal id is sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents each parameter in detail; the baseline is therefore 3. The description adds important cross-parameter semantics not present in the schema: only pass fields the user explicitly asked to change, omit everything else, and pass null only when explicitly clearing a field. That raises the score above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: propose an edit to an existing income or expense transaction. It also distinguishes itself from nearby tools by stating it only works on income/expense transactions and that transfers and asset trades are out of scope. The return outcome (a proposal id) and follow-up action are also clearly stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to use the tool (editing existing past income/expense transactions) and when not to (transfers and asset trades must be edited on the Transactions page). It also gives field-level usage rules: only pass changed fields, and use null only to clear a field. The confirmation workflow with confirm_proposal is spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_transferAInspect
Propose a transfer between two of the user's accounts. Same-currency transfers move the amount as-is; cross-currency transfers use the user-supplied exchangeRate or fall back to the exchange rate in force on the transfer date (the latest stored rate when the history does not reach back that far). Always include a short user-supplied description; optionally include a fee, recorded as a separate expense on the source account, or on the destination account when feeCurrency is the destination's currency. Returns a proposal id; to record it, call the confirm_proposal tool with the returned proposalId after the user approves (there is no UI button or proposal card to click in this context).
MCP note: this tool only CREATES a pending proposal; nothing is recorded until confirm_proposal is called.
| Name | Required | Description | Default |
|---|---|---|---|
| fee | No | Optional transfer fee, MAJOR units (e.g. 2.50 for $2.50, 1000 for Rp 1,000), in feeCurrency. Recorded as a separate expense on the account whose currency it is in. | |
| date | Yes | ||
| amount | Yes | Amount to transfer, in fromAccount currency, MAJOR units (the displayed amount, e.g. 100.00 for $100.00, 50000 for Rp 50,000). The server scales to storage units; never pre-multiply by 100. | |
| toCurrency | No | 3-letter currency for the destination amount. Required when toAccountId is omitted; otherwise inferred from the account. | |
| description | Yes | Short user-supplied description, e.g. 'rent to landlord'. | |
| feeCurrency | No | 3-letter currency of the fee: the source account's (the default, e.g. the sending bank's charge) or the destination account's (e.g. a receiving bank that takes its fee out of what arrives). The fee comes out of the account with that currency. Any other currency is refused: convert the fee into one of the two first. | |
| toAccountId | No | Id of the destination account. Omit only in the email-import pipeline when the email is genuinely ambiguous about the destination account. Do not pass an empty string; omit the field entirely instead. | |
| exchangeRate | No | ||
| fromCurrency | No | 3-letter currency for the source amount. Required when fromAccountId is omitted; otherwise inferred from the account. | |
| fromAccountId | No | Id of the source account. Omit only in the email-import pipeline when the email is genuinely ambiguous about the source account - the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead. | |
| editsProposalId | No | Id of an existing pending proposal to update in place. Pass this when the user asks to modify a proposal you previously made (e.g. 'make it $50 instead'). Look up current ids with listPendingProposals if you're unsure. Omit when proposing something new. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag this as a non-readonly, non-idempotent write, and the description goes well beyond them: nothing is recorded until confirm_proposal, the return value is a proposalId, the cross-currency rate falls back to the latest stored rate when history is short, and a fee becomes a separate expense on the fee-currency account. That is substantial behavioral context 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then currency handling, then the confirm handoff. It is dense with a long parenthetical clause, but each sentence carries information (fee placement, rate fallback, no-UI note) and nothing is redundant padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter mutation tool with no output schema, the description supplies the critical missing pieces: the pending-vs-recorded distinction, the returned proposalId, the confirm step, and the currency/rate/fee rules. An agent has everything needed to propose and later confirm correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 82% (baseline 3), yet the description adds real meaning beyond it — the exchangeRate fallback rule and the fee-as-separate-expense recording, plus the workflow framing for editsProposalId ('make it $50 instead'). Not every parameter is elaborated, but the highest-risk ones are.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Propose a transfer between two of the user's accounts') and immediately scopes it apart from the sibling propose_transaction by describing same- vs cross-currency handling. The trailing MCP note reinforces that it only creates a pending proposal, not a recorded transfer.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent to call confirm_proposal with the returned proposalId after user approval, and notes there is no UI button in this context, which is genuine routing guidance. It does not, however, compare itself against the many other propose_* siblings (transaction, asset_trade, budget) in when-to-use terms.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_insight_queryARead-onlyInspect
Compute an analytics insight (a single value, a comparison, a breakdown, or a time-series) over a filtered period of transactions, from an ad-hoc spec, WITHOUT saving it. Use this to answer one-off analytical questions like 'how much did I spend on Dining last month' or 'compare income vs expense this year'. If the user wants to keep the result pinned on their dashboard, use propose_saved_insight instead.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | The visualization spec describing what to compute. vizType is one of: 'value' (single number), 'comparison' (template income_vs_expense / period_over_period, or custom A/B), 'chart_time' (bucketed time series), 'breakdown' (top groups by category/envelope/account/type), 'table' (list of N transactions). Always set filter.period - use relative.value='this_month' / 'last_30_days' etc. unless the user gave explicit dates. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds the crucial non-persistence behavior ('WITHOUT saving it'), which annotations do not convey, and clarifies ad-hoc spec computation. It doesn't discuss rate limits or result format, but for a read-only compute tool that's acceptable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with what it computes, then usage examples, then the alternative. No waste. Slightly verbose in examples but each earns its place by grounding the abstract 'analytics insight' phrase.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a single required 'spec' parameter with 100% schema coverage, read-only annotations, and no output schema, the description covers purpose, usage, and the non-persistence behavior. The remaining minor gap is not naming the output shape for the agent, but the schema's vizType description handles that.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the embedded schema description already enumerates vizType values and gives period guidance (relative.value='this_month'). The description names the same output categories but doesn't add spec-syntax or default details beyond what the schema provides. Baseline 3 is correct when schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Compute) and resource (analytics insight) and enumerates the four output shapes (single value, comparison, breakdown, time-series). It explicitly contrasts with propose_saved_insight, making sibling differentiation unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear when-to-use condition (one-off analytical questions) with concrete examples, plus an explicit when-to-use-the-other-tool condition (keep result pinned on dashboard → propose_saved_insight). This is textbook alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_read_queryARead-onlyInspect
Run a single Postgres SELECT against the user's data. The database automatically scopes results to the current user - do NOT add WHERE user_id clauses. For net worth, budget, or asset valuation use the dedicated tools instead. Provide a one-line rationale explaining the query.
IMPORTANT - money units: columns the schema below marks as minor units (the marker reads roughly "[minor units - divide by 100]") are stored as INTEGER minor units - the real amount x 100, for ALL currencies including IDR with no decimal places (e.g. Rp 2,500,000 is stored as 250000000). Divide ONLY those marked columns by 100.0 in your SELECT projection to get real amounts, e.g. SELECT SUM(amount) / 100.0 AS total. Do NOT divide columns that are already in major units - in particular the v_chat_transaction view's amount_major and signed_amount are already real major-unit amounts, so use them directly (they are the easiest correct way to sum spend/income). Never multiply by 100.
asset
purchase_price is the per-unit purchase price in purchase_currency rows with deleted_at IS NOT NULL are soft-deleted; exclude them for per-asset P&L questions the getAssets tool already returns cost basis, P&L, and P&L % in the user's base currency; prefer it over hand-rolled SQL
id: string
user_id: string
account_id: string (nullable)
type: string [enum: crypto | stock | real_estate | vehicle | commodity | bond | other | collectible]
symbol: string (nullable)
display_symbol: string (nullable)
quantity: string
unit: string (nullable)
purchase_price: number (nullable) [minor units - divide by 100]
purchase_currency: string (nullable)
valuation_mode: string [enum: auto | manual]
created_at: date
updated_at: date
deleted_at: date (nullable)
asset_split
id: string
asset_id: string
effective_date: string
ratio_from: number
ratio_to: number
source: string [enum: manual | yahoo]
status: string [enum: pending | applied | dismissed]
cache_applied: boolean
created_at: date
updated_at: date
asset_transaction
per-trade buy/sell record for an asset; join transaction on transaction_id for date, type (buy | sell), currency, and soft-delete status (exclude deleted_at IS NOT NULL) price_per_unit is in the joined transaction's currency an asset conversion (swap of one asset for another) appears as a paired sell + buy on the same date whose transactions have account_id IS NULL; they move no cash (v_chat_transaction reports them as neutral with signed_amount 0)
id: string
transaction_id: string
asset_id: string
quantity: string
price_per_unit: number [minor units - divide by 100]
created_at: date
category
id: string
user_id: string
name: string
icon: string (nullable)
parent_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
collectible_acquisition
copies of a card added without a trade (with what was paid, not paid from an account): one row per time copies were added, dated acquired_on; join asset on asset_id (exclude soft-deleted assets) price_per_unit is what one copy cost, in currency; both are null when the user did not say what they paid a card's cost comes from these rows plus its buy trades in asset_transaction, never from asset.purchase_price; the getAssets tool already combines them into costBasis and P&L copies from a booster box or packs opened together have opening_id set: the box's price is split over them by market value (opening_weight), so price_per_unit is their share
id: string
asset_id: string
quantity: number
price_per_unit: number (nullable) [minor units - divide by 100]
currency: string (nullable)
acquired_on: string
opening_id: string (nullable)
opening_weight: number (nullable)
share_fixed: boolean
created_at: date
collectible_disposal
copies of a card that left the collection without a sale: reason 'removed' (taken out by the user) or 'traded' (given in a trade); join asset on asset_id (exclude soft-deleted assets) they leave at their average cost, so they realise no profit or loss; a sale is a sell trade in asset_transaction instead
id: string
asset_id: string
quantity: number
disposed_on: string
reason: string
created_at: date
collectible_holding
one row per trading card position: the asset (type = 'collectible') it belongs to, the exact print (variant_id) and how the card is held join asset on asset_id for quantity and deleted_at (exclude soft-deleted assets); join collectible_item_cache on variant_id for the card's name, set, number and language grading is 'raw' (then condition is nm | lp | mp | hp | dmg) or 'graded' (then grader is psa | bgs | cgc | sgc | tag | ace | ars | other and grade runs 1 to 10) a card's value: when asset.valuation_mode = 'auto' it is the market price, the price row with type 'collectible' and symbol = asset.symbol (collectible_price_meta on the same symbol says how it was built); when 'manual' it is the user's own latest valuation row, or, when source_pin is set, the price of that one pinned source, written into valuation each time it changes for value and P&L questions the getAssets tool (type 'collectible') already returns them in the user's base currency; prefer it
asset_id: string
variant_id: string
grading: string
condition: string (nullable)
grader: string (nullable)
grade: string (nullable)
grade_label: string (nullable)
source_pin: string (nullable)
notes: string (nullable)
created_at: date
updated_at: date
collectible_item_cache
one row per card print (global, no user data): game (pokemon, the only game Hundo supports for now), language (en | ja | id ...), set_code, set_name, card_number, card_name card_name is as printed (Japanese cards in Japanese); card_name_en holds the English name when there is one
variant_id: string
game: string
language: string
set_code: string
set_name: string
card_number: string
card_name: string
card_name_en: string (nullable)
finish: string
finish_label: string (nullable)
rarity: string (nullable)
image_url: string (nullable)
has_official_image: boolean
is_secret: boolean
status: string
synced_at: date
collectible_price_meta
one row per card market price (global, no user data), keyed by symbol = asset.symbol for a card ('ctg::'); the price itself is in price (type 'collectible', same symbol), in the currency the source quotes basis: market (one card market's recent sales price alone: TCGplayer in USD first, else Cardmarket in EUR), single_seller (1 shop), thin (the lower of 2 shops), median (trimmed median of 3 or more); source_count: how many sources the price is built from; observed_on: the day the newest evidence was seen stale = true or status <> 'priced' means the price is old: the source marked it stale, or no source prices the card any more and the last price is kept; estimate = true means a played copy priced from near mint times factor sources is a JSON array of {seller, platform, kind ('ask' = a shop's asking price, 'sold' = a real sale), price, currency, observedOn, counted}; price there is in minor units - divide by 100; counted = false means the source is listed but the price is not built from it (a market's sale beats the shops' asks); a missing counted means true
symbol: string
variant_id: string
condition_key: string
status: string
basis: string (nullable)
source_count: number
observed_on: string (nullable)
stale: boolean
estimate: boolean
derived_from: string (nullable)
factor: number (nullable)
sources: json
computed_at: date (nullable)
synced_at: date
history_synced_at: date (nullable)
counterparty
id: string
user_id: string
name: string
notes: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
envelope
this table represents what the user calls a 'budget' - the DB table is named
envelopefor historical reasons, but always say 'budget' in user-facing replies budgets are buckets that group categories - a user mentioning a budget by name (e.g., 'dating', 'vacation') almost always means a row here, not a category v_chat_transaction.envelope_name resolves the (transaction → budget) link for you; prefer that over joining envelope_category yourself
id: string
user_id: string
name: string
budgeted_amount: number [minor units - divide by 100]
currency: string
period: string [enum: weekly | monthly]
tracking_mode: string [enum: category | manual]
include_investments: boolean
category_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
envelope_category
junction table linking categories to budgets; v_chat_transaction.envelope_name already follows this path, so you rarely need to query envelope_category directly
id: string
envelope_id: string
category_id: string
exchange_rate
stored one direction per pair (typically from_currency = USD); for the reverse direction, use 1/rate from the existing row rather than expecting an explicit inverse row use the latest row per (from_currency, to_currency) pair (order by fetched_at DESC, limit 1)
id: string
from_currency: string
to_currency: string
rate: string
fetched_at: date
checked_at: date
financial_account
balance_cache is the live balance in minor units rows with deleted_at IS NOT NULL are soft-deleted; exclude them
id: string
user_id: string
name: string
type: string [enum: checking | savings | credit_card | cash | investment | crypto | loan | mortgage | property | vehicle | receivable | payable]
currency: string
institution: string (nullable)
icon: string (nullable)
is_liability: boolean
initial_balance: number [minor units - divide by 100]
balance_cache: number [minor units - divide by 100]
credit_limit: number (nullable) [minor units - divide by 100]
is_active: boolean
is_virtual: boolean
counterparty_id: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
net_worth_snapshot
id: string
user_id: string
total_assets: number [minor units - divide by 100]
total_liabilities: number [minor units - divide by 100]
net_worth: number [minor units - divide by 100]
currency: string
breakdown_json: json (nullable)
snapshot_date: string
created_at: date
price
cached market price per unit of the asset, keyed by (type, symbol); join asset on both columns use the latest row per (type, symbol) (order by fetched_at DESC, limit 1) for commodities the price is per troy ounce regardless of asset.unit; convert before comparing to per-unit purchase prices
id: string
type: string [enum: crypto | stock | real_estate | vehicle | commodity | bond | other | collectible]
symbol: string
price: number [minor units - divide by 100]
currency: string
fetched_at: date
transaction
amount is always non-negative; direction lives in type for spend/income totals, prefer the v_chat_transaction view rows with deleted_at IS NOT NULL are soft-deleted; exclude them
id: string
user_id: string
account_id: string (nullable)
type: string [enum: income | expense | transfer | buy | sell]
amount: number [minor units - divide by 100]
currency: string
exchange_rate: string (nullable)
date: string
description: string (nullable)
category_id: string (nullable)
envelope_id: string (nullable)
notes: string (nullable)
receipt_image_url: string (nullable)
created_at: date
updated_at: date
deleted_at: date (nullable)
valuation
one row per asset per day; latest row is the current value
id: string
asset_id: string
value: number [minor units - divide by 100]
currency: string
fetched_at: date
v_chat_transaction (view)
Presentation view over transaction. Soft-deleted rows already excluded. Prefer this over the raw transaction table for any spend/income question.
id: string
user_id: string
date: date
type: string [enum: income | expense | transfer | buy | sell]
description: string (nullable)
currency: string
amount_major: numeric (always >= 0; major units, e.g. dollars not cents)
signed_amount: numeric (negative for expense and buy; positive for income and sell; zero for transfer and for cashless asset-conversion legs)
direction: string [enum: outflow | inflow | neutral]
account_id: string (nullable; null on the cashless legs of an asset conversion, which move no cash)
category_id: string (nullable)
category_name: string (nullable, joined from category)
envelope_id: string (nullable; direct budget assignment, often null)
envelope_name: string (nullable; the user's budget label, resolved via direct envelope_id OR via the envelope_category junction on category_id - match this when the user names a budget like "Dating")
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single Postgres SELECT statement. | |
| rationale | Yes | One-line plain-English explanation of why you're running this query. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. Beyond that, the description discloses non-obvious behavior: automatic per-user result scoping (a security-relevant trait), minor-vs-major unit storage rules with the IDR exception, soft-delete exclusions, and view-vs-table preferences. This is far richer than the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded correctly: purpose, then the critical unit/scoping rules, then reference schema. The length is extreme, but for a raw-SQL tool the inline table/column documentation earns its place by preventing hallucinated columns. Slightly penalized for sheer bulk that an agent must wade through.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema and the tool is maximally complex (25+ tables), yet the description supplies the full table catalog, unit conventions, join hints, soft-delete rules, and a preferred view. Nothing material an agent needs to write a correct query is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds constraints beyond the schema: the query must be a single SELECT, must not add a user_id filter, and the rationale should be a one-line plain-English explanation. That meaningfully sharpens both parameters rather than repeating them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource: 'Run a single Postgres SELECT against the user's data.' It immediately scopes the tool and differentiates from siblings by naming the alternative ('For net worth, budget, or asset valuation use the dedicated tools instead'). An agent can tell this apart from get_net_worth or run_insight_query without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use (arbitrary read over user data), when-not ('use the dedicated tools instead' for net worth/budget/valuation), and hard constraints ('do NOT add WHERE user_id clauses', prefer v_chat_transaction, prefer getAssets for P&L). It also routes away from hand-rolled SQL where a dedicated tool exists, which is exactly the 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.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
- Changed
propose_asset_convert4 fields changed- changed
Input schema / properties / destAssetId / descriptionPrevious value: -"Id of the asset being acquired (the destination). Must already exist and differ from the source."New value: +"Id of the asset being acquired (the destination), from getAssets. Must differ from the source. Omit it only when the user does not hold the destination yet, and pass newDestAsset instead." - added
Input schema / properties / destAssetId / minLengthAdded value: +1 - added
Input schema / properties / newDestAssetAdded value: +{ + "description": "Pass this INSTEAD of an asset id, only when getAssets shows the user does not hold the asset yet. Confirming the proposal creates the asset and records the trade together.", + "properties": { + "symbol": { + "description": "For crypto and stock: the ticker, e.g. \"SOL\" or \"AAPL\" (use the exchange suffix for a non-US listing, e.g. \"BBCA.JK\"). For commodity: \"gold\", \"silver\", \"platinum\" or \"palladium\". For every other type: a short name the user will recognize, e.g. \"Honda Civic 2021\".", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "type": { + "description": "crypto, stock (shares, ETFs and funds), commodity (gold, silver, platinum, palladium), real_estate, vehicle, bond or other.", + "enum": [ + "crypto", + "stock", + "commodity", + "real_estate", + "vehicle", + "bond", + "other" + ], + "type": "string" + }, + "unit": { + "description": "Commodity only, and then required: the unit the quantity is in - toz (troy ounce), g or kg. The price per unit must be in the same unit.", + "enum": [ + "toz", + "g", + "kg" + ], + "type": "string" + }, + "valuationMode": { + "description": "Omit it. \"auto\" (the default for crypto, stock and commodity) follows the market price. Pass \"manual\" only when the user says the asset has no market price, e.g. shares in a private company; its value is then the price paid until the user updates it.", + "enum": [ + "auto", + "manual" + ], + "type": "string" + } + }, + "required": [ + "type", + "symbol" + ], + "type": "object" +} - changed
Input schema / requiredPrevious value: -[ - "sourceAssetId", - "sourceQuantity", - "destAssetId", - "destQuantity", - "date", - "description" -]New value: +[ + "sourceAssetId", + "sourceQuantity", + "destQuantity", + "date", + "description" +]
- Changed
propose_asset_trade4 fields changed- changed
Input schema / properties / assetId / descriptionPrevious value: -"Id of the asset being traded."New value: +"Id of the asset being traded, from getAssets. Omit it only for a buy of an asset the user does not hold yet, and pass newAsset instead." - added
Input schema / properties / assetId / minLengthAdded value: +1 - added
Input schema / properties / newAssetAdded value: +{ + "description": "Pass this INSTEAD of an asset id, only when getAssets shows the user does not hold the asset yet. Confirming the proposal creates the asset and records the trade together.", + "properties": { + "symbol": { + "description": "For crypto and stock: the ticker, e.g. \"SOL\" or \"AAPL\" (use the exchange suffix for a non-US listing, e.g. \"BBCA.JK\"). For commodity: \"gold\", \"silver\", \"platinum\" or \"palladium\". For every other type: a short name the user will recognize, e.g. \"Honda Civic 2021\".", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "type": { + "description": "crypto, stock (shares, ETFs and funds), commodity (gold, silver, platinum, palladium), real_estate, vehicle, bond or other.", + "enum": [ + "crypto", + "stock", + "commodity", + "real_estate", + "vehicle", + "bond", + "other" + ], + "type": "string" + }, + "unit": { + "description": "Commodity only, and then required: the unit the quantity is in - toz (troy ounce), g or kg. The price per unit must be in the same unit.", + "enum": [ + "toz", + "g", + "kg" + ], + "type": "string" + }, + "valuationMode": { + "description": "Omit it. \"auto\" (the default for crypto, stock and commodity) follows the market price. Pass \"manual\" only when the user says the asset has no market price, e.g. shares in a private company; its value is then the price paid until the user updates it.", + "enum": [ + "auto", + "manual" + ], + "type": "string" + } + }, + "required": [ + "type", + "symbol" + ], + "type": "object" +} - changed
Input schema / requiredPrevious value: -[ - "action", - "assetId", - "quantity", - "pricePerUnit", - "priceCurrency", - "date", - "description" -]New value: +[ + "action", + "quantity", + "pricePerUnit", + "priceCurrency", + "date", + "description" +]
1 tool update
- Changed
propose_transfer2 fields changed- changed
Input schema / properties / fee / descriptionPrevious value: -"Optional transfer fee in fromAccount currency, MAJOR units (e.g. 2.50 for $2.50, 1000 for Rp 1,000). Recorded as a separate expense on the source account."New value: +"Optional transfer fee, MAJOR units (e.g. 2.50 for $2.50, 1000 for Rp 1,000), in feeCurrency. Recorded as a separate expense on the account whose currency it is in." - added
Input schema / properties / feeCurrencyAdded value: +{ + "description": "3-letter currency of the fee: the source account's (the default, e.g. the sending bank's charge) or the destination account's (e.g. a receiving bank that takes its fee out of what arrives). The fee comes out of the account with that currency. Any other currency is refused: convert the fee into one of the two first.", + "maxLength": 3, + "minLength": 3, + "type": "string" +}
4 tool updates
- Changed
propose_asset_trade1 field changed- changed
Input schema / properties / cashAccountId / descriptionPrevious value: -"Id of the cash account funding or receiving this trade. Omit only in the email-import pipeline when the source email doesn't make it clear which cash account is involved — the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead."New value: +"Id of the cash account funding or receiving this trade. Omit only in the email-import pipeline when the source email doesn't make it clear which cash account is involved - the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead."
- Changed
propose_saved_insight1 field changed- changed
Input schema / properties / spec / descriptionPrevious value: -"The visualization spec. vizType is one of: 'value' (single number), 'comparison' (template income_vs_expense / period_over_period, or custom A/B), 'chart_time' (bucketed time series), 'breakdown' (top groups by category/envelope/account/type), 'table' (list of N transactions). Always set the filter.period — use relative.value='this_month' / 'last_30_days' etc. unless the user gave explicit dates."New value: +"The visualization spec. vizType is one of: 'value' (single number), 'comparison' (template income_vs_expense / period_over_period, or custom A/B), 'chart_time' (bucketed time series), 'breakdown' (top groups by category/envelope/account/type), 'table' (list of N transactions). Always set the filter.period - use relative.value='this_month' / 'last_30_days' etc. unless the user gave explicit dates."
- Changed
propose_transaction2 fields changed- changed
Input schema / properties / accountId / descriptionPrevious value: -"Id of the financial account. Omit only in the email-import pipeline when the source email is genuinely ambiguous about which account paid — the user will pick one via the proposal card's Edit button. In chat, always pass a real id (ask the user instead of guessing). Do not pass an empty string; omit the field entirely instead."New value: +"Id of the financial account. Omit only in the email-import pipeline when the source email is genuinely ambiguous about which account paid - the user will pick one via the proposal card's Edit button. In chat, always pass a real id (ask the user instead of guessing). Do not pass an empty string; omit the field entirely instead." - changed
Input schema / properties / exchangeRate / descriptionPrevious value: -"Rate to convert the amount into the account's currency, used only when currency differs from the account. Usually omit — the server auto-fetches the rate as of the date."New value: +"Rate to convert the amount into the account's currency, used only when currency differs from the account. Usually omit - the server auto-fetches the rate as of the date."
- Changed
propose_transfer1 field changed- changed
Input schema / properties / fromAccountId / descriptionPrevious value: -"Id of the source account. Omit only in the email-import pipeline when the email is genuinely ambiguous about the source account — the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead."New value: +"Id of the source account. Omit only in the email-import pipeline when the email is genuinely ambiguous about the source account - the user picks one via the Edit button. In chat, always pass a real id. Do not pass an empty string; omit the field entirely instead."
1 tool update
- Changed
get_assets2 fields changed- changed
Input schema / properties / type / descriptionPrevious value: -"Filter by asset type. Omit to return all."New value: +"Filter by asset type. Omit to return all. 'collectible' is the user's trading cards." - changed
Input schema / properties / type / enumPrevious value: -[ - "crypto", - "stock", - "real_estate", - "vehicle", - "commodity", - "bond", - "other" -]New value: +[ + "crypto", + "stock", + "real_estate", + "vehicle", + "commodity", + "bond", + "other", + "collectible" +]
1 tool update
- Changed
propose_budget_upsert5 fields changed- added
Input schema / properties / categoryId / descriptionAdded value: +"Shorthand for a categoryIds list of one. It is NOT an 'add this one' field: on update it replaces the budget's whole category list, exactly as categoryIds does, so a budget that already covers several would be left with just this one. To add a category to an existing budget, read its current list and pass all of them in categoryIds." - added
Input schema / properties / categoryIdsAdded value: +{ + "description": "Every category this budget tracks, e.g. rent + groceries + transport + utilities for one 'Needs' budget. On update this REPLACES the budget's current categories, so pass the full list you want, not just the new ones. Look ids up first; never invent them.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / includeInvestmentsAdded value: +{ + "description": "Count asset purchases (buys) toward this budget's spending alongside expenses. Defaults to false on create; left unchanged on update unless you pass it.", + "type": "boolean" +} - added
Input schema / properties / periodAdded value: +{ + "description": "How often the allocation resets. Defaults to 'monthly' on create; left unchanged on update unless you pass it.", + "enum": [ + "weekly", + "monthly" + ], + "type": "string" +} - added
Input schema / properties / trackingModeAdded value: +{ + "description": "'category' counts spending from the linked categories automatically; 'manual' counts nothing and the user records progress by hand. Omit on create and it follows whether you passed categories.", + "enum": [ + "category", + "manual" + ], + "type": "string" +}
21 tool updates
- First observed
confirm_proposal - First observed
get_accounts - First observed
get_assets - First observed
get_budget - First observed
get_categories - First observed
get_ious - First observed
get_net_worth - First observed
get_saved_insights - First observed
get_yesterday_summary - First observed
list_pending_proposals - First observed
propose_asset_convert - First observed
propose_asset_trade - First observed
propose_budget_upsert - First observed
propose_delete_budget - First observed
propose_delete_transaction - First observed
propose_saved_insight - First observed
propose_transaction - First observed
propose_transaction_edit - First observed
propose_transfer - First observed
run_insight_query - First observed
run_read_query
Related MCP Connectors
Read-only access to your net worth, wealth percentile, projections, splits and budget.
Chat with your bank data: balances, transactions, budgets, bills. Reads only, never moves money.
Personal finance by conversation: expenses, receipts, statement import, budgets, net worth.
Read-only access to your bank, investment, and crypto accounts: balances, transactions, holdings.
Related MCP Servers
- AlicenseAqualityBmaintenanceEnables read-only access to YNAB budgets, accounts, categories, payees, and transactions, plus safe addition of unapproved transactions with dry-run and duplicate pre-check for reconciliation workflows.6MIT
- AlicenseAqualityBmaintenanceLets an AI agent read a YNAB budget and ask plain-language questions about it — where money went, what still needs a category, whether an account matches the bank, and when money would run out. Changes such as categorising, splitting or budgeting are read-only by default, and every write is previewed, explicitly confirmed, and undoable.12MIT
- AlicenseAqualityBmaintenanceEnables AI assistants to interact with YNAB budgets, performing read-only queries by default and optional write operations like creating transactions and managing categories through natural language.39259 npm34MIT
- AlicenseNot gradedqualityCmaintenanceEnables natural-language management of personal finances, including reading balances grouped by bank/category/card/month, retrieving transactions filtered by date and keyword, listing banks/cards/categories, creating/updating/deleting transactions individually or in bulk, and transferring money between the user's own accounts.MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.