add_flow
Record or update a single cash flow entry (income or expense) for a specific period. Supports predefined categories like salary, rent, insurance, and debt repayment.
Instructions
Upsert a single cash flow entry for a period (also edits — same composite key overwrites). Use this when recording just one or two flow items; prefer add_monthly for full month-end settlement. VALID FLOW CATEGORIES — income (sub_type=employment): salary, business. Income (sub_type=investment): dividends, interest. Income (sub_type=other): income_other. Expense (sub_type=consumption): personal. Expense (sub_type=fixed): insurance, phone, utilities. Expense (sub_type=housing): rent, maintenance. Expense (sub_type=debt): loan_repayment. Expense (sub_type=other): expense_other. Use ONLY these category strings — do NOT invent your own.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| period | Yes | YYYY-MM | |
| date | Yes | YYYY-MM-DD (typically month-end) | |
| type | Yes | ||
| sub_type | Yes | Must match the category sub_type. Income: employment, investment, other. Expense: consumption, fixed, housing, debt, other. | |
| category | Yes | Must be one of the predefined flow categories. Income: salary, business, dividends, interest, income_other. Expense: personal, insurance, phone, utilities, rent, maintenance, loan_repayment, expense_other. | |
| amount | Yes | Amount in `currency` units (whole units) | |
| currency | No | Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Non-USD converted via historical FX at `date`. | USD |
| memo | No |
Implementation Reference
- apps/mcp/src/tools/mutate.ts:171-199 (handler)Handler function for the 'add_flow' tool. Upserts a single cash flow entry with currency conversion via nativeToUsd and onConflictDoUpdate composite key strategy.
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(flowEntries) .values({ period, date, type, sub_type, category, amount: conv.usd, currency: 'USD', memo: memo ?? null, }) .onConflictDoUpdate({ target: [flowEntries.period, flowEntries.type, flowEntries.sub_type, flowEntries.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:150-170 (schema)Zod input schema for the 'add_flow' tool defining period, date, type, sub_type, category, amount, currency, and memo fields.
{ period: z.string().describe('YYYY-MM'), date: z.string().describe('YYYY-MM-DD (typically month-end)'), type: z.enum(['income', 'expense']), sub_type: z .string() .describe( 'Must match the category sub_type. Income: employment, investment, other. Expense: consumption, fixed, housing, debt, other.', ), category: z .string() .describe( 'Must be one of the predefined flow categories. Income: salary, business, dividends, interest, income_other. Expense: personal, insurance, phone, utilities, rent, maintenance, loan_repayment, expense_other.', ), amount: z.number().int().describe('Amount in `currency` units (whole units)'), currency: z .string() .default('USD') .describe('Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Non-USD converted via historical FX at `date`.'), memo: z.string().optional(), }, - apps/mcp/src/tools/mutate.ts:147-199 (registration)Registration of 'add_flow' tool with the MCP server via server.tool() in registerMutateTools function.
server.tool( 'add_flow', 'Upsert a single cash flow entry for a period (also edits — same composite key overwrites). Use this when recording just one or two flow items; prefer add_monthly for full month-end settlement. VALID FLOW CATEGORIES — income (sub_type=employment): salary, business. Income (sub_type=investment): dividends, interest. Income (sub_type=other): income_other. Expense (sub_type=consumption): personal. Expense (sub_type=fixed): insurance, phone, utilities. Expense (sub_type=housing): rent, maintenance. Expense (sub_type=debt): loan_repayment. Expense (sub_type=other): expense_other. Use ONLY these category strings — do NOT invent your own.', { period: z.string().describe('YYYY-MM'), date: z.string().describe('YYYY-MM-DD (typically month-end)'), type: z.enum(['income', 'expense']), sub_type: z .string() .describe( 'Must match the category sub_type. Income: employment, investment, other. Expense: consumption, fixed, housing, debt, other.', ), category: z .string() .describe( 'Must be one of the predefined flow categories. Income: salary, business, dividends, interest, income_other. Expense: personal, insurance, phone, utilities, rent, maintenance, loan_repayment, expense_other.', ), amount: z.number().int().describe('Amount in `currency` units (whole units)'), currency: z .string() .default('USD') .describe('Currency of `amount` (USD/KRW/JPY/EUR/CNY/GBP). Non-USD converted via historical FX at `date`.'), 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(flowEntries) .values({ period, date, type, sub_type, category, amount: conv.usd, currency: 'USD', memo: memo ?? null, }) .onConflictDoUpdate({ target: [flowEntries.period, flowEntries.type, flowEntries.sub_type, flowEntries.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:12-12 (registration)Parent function that registers all mutate tools including add_flow.
export function registerMutateTools(server: McpServer): void { - apps/mcp/src/helpers.ts:79-90 (helper)Helper function nativeToUsd used by the add_flow handler to convert amounts from native currency to USD using cached FX rates.
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) }; };