Skip to main content
Glama

Hundo

run_read_query

Read-only

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]

  • 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)

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 envelope for 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

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]

  • 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")

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sqlYesA single Postgres SELECT statement.
rationaleYesOne-line plain-English explanation of why you're running this query.

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.8/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description discloses that the database auto-scopes to the current user, that domestic money columns are stored in minor units and must be divided by 100.0, that soft-deleted rows must be excluded, and that v_chat_transaction already excludes them. These are behavioral quirks the annotation alone would not reveal.

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

Conciseness4/5

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

The description is long, but it is organized: an initial purpose statement, an all-caps money-unit warning, and then a table-by-table schema with compressed inline comments. Every sentence serves as either a necessary constraint (deleted_at, minor units, view preference) or a pointer to a better tool. Although dense, the length is justified by the complexity of allowing raw SQL.

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

Completeness5/5

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

For a tool that exposes full SQL to a complex schema and has no defined output schema, the description covers all necessary ground: schema, enums, soft-delete flags, money-unit conversion, view preference, and dedicated-tool routing. It leaves an agent almost fully equipped to write correct SELECT statements without previous domain knowledge.

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

Parameters4/5

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

The input schema only labels `sql` as 'a single Postgres SELECT statement.' The description adds extensive meaning to that parameter: which columns are minor units, how to divide them, how to interpret signed_amount/amount_major, and which tables/views to prefer. This goes beyond the schema's coverage and gives the agent the domain knowledge needed to build correct queries.

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

Purpose5/5

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

The description opens with a specific action and resource: 'Run a single Postgres SELECT against the user's data.' It also separates itself from dedicated tools by saying 'For net worth, budget, or asset valuation use the dedicated tools instead,' naming siblings like get_net_worth, get_budget, and get_assets. This makes its purpose and boundary unmistakable.

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

Usage Guidelines5/5

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

The description explicitly tells the agent when not to use the tool ('use the dedicated tools instead') and when to prefer it ('for spend/income totals, prefer the v_chat_transaction view'). It also forbids adding YES WHERE user_id clauses and mandates a `rationale` parameter, giving concrete conditions for invocation.

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

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources