add_balance
Add or edit one balance sheet line item by providing period, category, amount, and currency. Non-USD amounts are converted to USD using historical rates.
Instructions
Upsert a single balance sheet entry for a period (also edits — same composite key overwrites). Use this when recording just one or two balance items; prefer add_monthly for full month-end settlement. sub_type: cash|investment|other (assets) or short_term|long_term (liabilities). VALID BALANCE CATEGORIES — assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other. Use ONLY these category strings — do NOT invent your own. For non-USD users: pass currency as the native currency (e.g. "KRW") and amount in that currency — converted to USD via historical FX at date. Use category="usd_cash" (currency="USD") for USD-denominated assets held alongside home-currency ones.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| period | Yes | YYYY-MM | |
| date | Yes | YYYY-MM-DD (typically month-end) | |
| type | Yes | ||
| sub_type | Yes | cash | investment | other | short_term | long_term | |
| category | Yes | Must be one of the predefined balance categories: assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other. | |
| amount | Yes | Amount in `currency` units (whole units, no decimals) | |
| currency | No | Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Defaults to USD. Non-USD values are converted to USD via historical FX at `date` for storage. | USD |
| memo | No |
Implementation Reference
- apps/mcp/src/tools/mutate.ts:87-145 (handler)The 'add_balance' MCP tool handler: Upserts a single balance sheet entry. Validates inputs via Zod schema (period, date, type, sub_type, category, amount, currency, memo), converts non-USD amounts via nativeToUsd helper using historical FX at the given date, then inserts/upserts into the balanceEntries table using onConflictDoUpdate on the composite key (period, type, sub_type, category). Returns the stored USD amount along with native amount/currency if conversion occurred.
server.tool( 'add_balance', 'Upsert a single balance sheet entry for a period (also edits — same composite key overwrites). Use this when recording just one or two balance items; prefer add_monthly for full month-end settlement. sub_type: cash|investment|other (assets) or short_term|long_term (liabilities). VALID BALANCE CATEGORIES — assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other. Use ONLY these category strings — do NOT invent your own. For non-USD users: pass `currency` as the native currency (e.g. "KRW") and `amount` in that currency — converted to USD via historical FX at `date`. Use category="usd_cash" (currency="USD") for USD-denominated assets held alongside home-currency ones.', { period: z.string().describe('YYYY-MM'), date: z.string().describe('YYYY-MM-DD (typically month-end)'), type: z.enum(['asset', 'liability']), sub_type: z.string().describe('cash | investment | other | short_term | long_term'), category: z .string() .describe( 'Must be one of the predefined balance categories: assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other.', ), amount: z.number().int().describe('Amount in `currency` units (whole units, no decimals)'), currency: z .string() .default('USD') .describe( 'Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Defaults to USD. Non-USD values are converted to USD via historical FX at `date` for storage.', ), memo: z.string().optional(), }, async ({ period, date, type, sub_type, category, amount, currency, memo }) => { const conv = nativeToUsd(amount, currency, date); if (!conv.ok) return err(conv.error); const db = getDb(); db.insert(balanceEntries) .values({ period, date, type, sub_type, category, amount: conv.usd, currency: 'USD', memo: memo ?? null, }) .onConflictDoUpdate({ target: [ balanceEntries.period, balanceEntries.type, balanceEntries.sub_type, balanceEntries.category, ], set: { amount: conv.usd, currency: 'USD', date, memo: memo ?? null }, }) .run(); const cur = currency.toUpperCase(); return ok({ period, type, sub_type, category, amount: conv.usd, currency: 'USD', ...(cur !== 'USD' ? { native_amount: amount, native_currency: cur } : {}), }); }, ); - apps/mcp/src/tools/mutate.ts:90-108 (schema)Zod input schema for 'add_balance': period (YYYY-MM), date (YYYY-MM-DD), type (asset|liability), sub_type (string), category (string from predefined lists), amount (integer in currency units), currency (defaults to USD), and optional memo.
{ period: z.string().describe('YYYY-MM'), date: z.string().describe('YYYY-MM-DD (typically month-end)'), type: z.enum(['asset', 'liability']), sub_type: z.string().describe('cash | investment | other | short_term | long_term'), category: z .string() .describe( 'Must be one of the predefined balance categories: assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other.', ), amount: z.number().int().describe('Amount in `currency` units (whole units, no decimals)'), currency: z .string() .default('USD') .describe( 'Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Defaults to USD. Non-USD values are converted to USD via historical FX at `date` for storage.', ), memo: z.string().optional(), }, - apps/mcp/src/tools/mutate.ts:87-145 (registration)Tool registration via server.tool('add_balance', ...) inside registerMutateTools(), which is called from apps/mcp/src/index.ts at line 21.
server.tool( 'add_balance', 'Upsert a single balance sheet entry for a period (also edits — same composite key overwrites). Use this when recording just one or two balance items; prefer add_monthly for full month-end settlement. sub_type: cash|investment|other (assets) or short_term|long_term (liabilities). VALID BALANCE CATEGORIES — assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other. Use ONLY these category strings — do NOT invent your own. For non-USD users: pass `currency` as the native currency (e.g. "KRW") and `amount` in that currency — converted to USD via historical FX at `date`. Use category="usd_cash" (currency="USD") for USD-denominated assets held alongside home-currency ones.', { period: z.string().describe('YYYY-MM'), date: z.string().describe('YYYY-MM-DD (typically month-end)'), type: z.enum(['asset', 'liability']), sub_type: z.string().describe('cash | investment | other | short_term | long_term'), category: z .string() .describe( 'Must be one of the predefined balance categories: assets: cash, savings, housing_sub, usd_cash, cash_other, domestic_stock, overseas_stock, real_estate, pension, vehicle, deposit, asset_other. Liabilities: credit_card, short_term_other, loan, long_term_other.', ), amount: z.number().int().describe('Amount in `currency` units (whole units, no decimals)'), currency: z .string() .default('USD') .describe( 'Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Defaults to USD. Non-USD values are converted to USD via historical FX at `date` for storage.', ), memo: z.string().optional(), }, async ({ period, date, type, sub_type, category, amount, currency, memo }) => { const conv = nativeToUsd(amount, currency, date); if (!conv.ok) return err(conv.error); const db = getDb(); db.insert(balanceEntries) .values({ period, date, type, sub_type, category, amount: conv.usd, currency: 'USD', memo: memo ?? null, }) .onConflictDoUpdate({ target: [ balanceEntries.period, balanceEntries.type, balanceEntries.sub_type, balanceEntries.category, ], set: { amount: conv.usd, currency: 'USD', date, memo: memo ?? null }, }) .run(); const cur = currency.toUpperCase(); return ok({ period, type, sub_type, category, amount: conv.usd, currency: 'USD', ...(cur !== 'USD' ? { native_amount: amount, native_currency: cur } : {}), }); }, ); - apps/mcp/src/index.ts:21-21 (registration)Top-level registration call: registerMutateTools(server) which registers all mutation tools including 'add_balance'.
registerMutateTools(server); - apps/mcp/src/helpers.ts:78-90 (helper)The nativeToUsd helper used by add_balance: converts amount from a given currency to USD using historical FX rates from the data repository at the given date.
type ConvertResult = { ok: true; usd: number } | { ok: false; error: string }; export const nativeToUsd = (amount: number, currency: string, date: string): ConvertResult => { const cur = currency.toUpperCase(); if (cur === 'USD') return { ok: true, usd: amount }; const row = getRepository().fx.getRateOnOrBefore(date, cur); if (!row || row.rate_to_usd == null) { return { ok: false, error: `No FX rate cached for ${cur} on or before ${date}. Run sync_fx_rates first, or pass currency="USD" with the converted amount.`, }; } return { ok: true, usd: Math.round(amount / row.rate_to_usd) }; };