Skip to main content
Glama

report_flow

Aggregate cash flow entries by period to view monthly income, expenses, net flow, and savings rate.

Instructions

Aggregate cash flow entries by period. Returns monthly income, expenses, net flow, and savings rate sorted by period.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoMax number of periods to return (default: 36)

Implementation Reference

  • The handler function for the 'report_flow' tool. It queries all flow entries from the database, aggregates them by period into income/expenses, sorts by period, limits results, and returns each period with income, expenses, net_flow, and savings_rate.
    server.tool(
      'report_flow',
      'Aggregate cash flow entries by period. Returns monthly income, expenses, net flow, and savings rate sorted by period.',
      {
        limit: z
          .number()
          .int()
          .min(1)
          .max(120)
          .default(36)
          .describe('Max number of periods to return (default: 36)'),
      },
      async ({ limit }) => {
        const db = getDb();
        const rows = db.select().from(flowEntries).all();
    
        const byPeriod = rows.reduce((map, { period, type, amount }) => {
          const prev = map.get(period) ?? { income: 0, expenses: 0 };
          map.set(period, {
            income: prev.income + (type === 'income' ? amount : 0),
            expenses: prev.expenses + (type === 'expense' ? amount : 0),
          });
          return map;
        }, new Map<string, { income: number; expenses: number }>());
    
        const result = [...byPeriod.entries()]
          .sort(([a], [b]) => a.localeCompare(b))
          .slice(-limit)
          .map(([period, { income, expenses }]) => ({
            period,
            income,
            expenses,
            net_flow: income - expenses,
            savings_rate: income > 0 ? ((income - expenses) / income) * 100 : null,
          }));
    
        return ok(result);
      },
    );
  • Input schema for report_flow: takes an optional 'limit' parameter (int, 1-120, default 36) controlling how many periods to return.
    {
      limit: z
        .number()
        .int()
        .min(1)
        .max(120)
        .default(36)
        .describe('Max number of periods to return (default: 36)'),
    },
  • Registration of report tools (including report_flow) on the MCP server.
    registerReportTools(server);
  • Registration function that registers report_flow (and other report tools) on the MCP server.
    export function registerReportTools(server: McpServer): void {
      server.tool(
        'report_balance',
        'Aggregate balance sheet entries by period. Returns monthly net worth trend sorted by period.',
        {
          limit: z
            .number()
            .int()
            .min(1)
            .max(120)
            .default(36)
            .describe('Max number of periods to return (default: 36)'),
        },
        async ({ limit }) => {
          const db = getDb();
          const rows = db.select().from(balanceEntries).all();
    
          const byPeriod = rows.reduce((map, { period, type, amount }) => {
            const prev = map.get(period) ?? { assets: 0, liabilities: 0 };
            map.set(period, {
              assets: prev.assets + (type === 'asset' ? amount : 0),
              liabilities: prev.liabilities + (type === 'liability' ? amount : 0),
            });
            return map;
          }, new Map<string, { assets: number; liabilities: number }>());
    
          const result = [...byPeriod.entries()]
            .sort(([a], [b]) => a.localeCompare(b))
            .slice(-limit)
            .map(([period, { assets, liabilities }]) => ({
              period,
              assets,
              liabilities,
              net_worth: assets - liabilities,
            }));
    
          return ok(result);
        },
      );
    
      server.tool(
        'report_flow',
        'Aggregate cash flow entries by period. Returns monthly income, expenses, net flow, and savings rate sorted by period.',
        {
          limit: z
            .number()
            .int()
            .min(1)
            .max(120)
            .default(36)
            .describe('Max number of periods to return (default: 36)'),
        },
        async ({ limit }) => {
          const db = getDb();
          const rows = db.select().from(flowEntries).all();
    
          const byPeriod = rows.reduce((map, { period, type, amount }) => {
            const prev = map.get(period) ?? { income: 0, expenses: 0 };
            map.set(period, {
              income: prev.income + (type === 'income' ? amount : 0),
              expenses: prev.expenses + (type === 'expense' ? amount : 0),
            });
            return map;
          }, new Map<string, { income: number; expenses: number }>());
    
          const result = [...byPeriod.entries()]
            .sort(([a], [b]) => a.localeCompare(b))
            .slice(-limit)
            .map(([period, { income, expenses }]) => ({
              period,
              income,
              expenses,
              net_flow: income - expenses,
              savings_rate: income > 0 ? ((income - expenses) / income) * 100 : null,
            }));
    
          return ok(result);
        },
      );
    
      server.tool(
        'report_settle',
        'Read-only summary for a period: balance sheet + cash flow entries with computed totals (net_worth, net_flow). Use this to review month-end settlement results after entries are recorded via add_balance / add_flow. Defaults to the current month.',
        {
          period: z.string().optional().describe('Period in YYYY-MM format (defaults to current month)'),
        },
        async ({ period }) => {
          const db = getDb();
          const targetPeriod = period ?? localPeriod();
    
          const balEntries = db
            .select()
            .from(balanceEntries)
            .where(eq(balanceEntries.period, targetPeriod))
            .all();
          const flowEnts = db
            .select()
            .from(flowEntries)
            .where(eq(flowEntries.period, targetPeriod))
            .all();
    
          const sumBy = (entries: typeof balEntries | typeof flowEnts, type: string) =>
            entries.filter((e) => e.type === type).reduce((s, e) => s + e.amount, 0);
    
          const total_assets = sumBy(balEntries, 'asset');
          const total_liabilities = sumBy(balEntries, 'liability');
          const total_income = sumBy(flowEnts, 'income');
          const total_expenses = sumBy(flowEnts, 'expense');
    
          return ok({
            period: targetPeriod,
            balance: {
              entries: balEntries,
              total_assets,
              total_liabilities,
              net_worth: total_assets - total_liabilities,
            },
            flow: {
              entries: flowEnts,
              total_income,
              total_expenses,
              net_flow: total_income - total_expenses,
            },
          });
        },
      );
    
      server.tool(
        'report_combined',
        'Both balance sheet and cash flow trends in one call. Equivalent to calling report_balance and report_flow with the same limit. **Primary entry point for wealth-trajectory questions** ("are my assets growing?", "is my net worth trending up?", "am I getting wealthier?") — portfolio value alone is insufficient because it ignores cash savings, debt paydown, and savings rate. Pair with show_snapshot when the user wants the market-driven slice of the trajectory.',
        {
          limit: z
            .number()
            .int()
            .min(1)
            .max(120)
            .default(36)
            .describe('Max number of periods per report (default: 36)'),
        },
        async ({ limit }) => {
          const db = getDb();
          const balRows = db.select().from(balanceEntries).all();
          const flowRows = db.select().from(flowEntries).all();
    
          const aggBal = [
            ...balRows
              .reduce((map, { period, type, amount }) => {
                const prev = map.get(period) ?? { assets: 0, liabilities: 0 };
                return map.set(period, {
                  assets: prev.assets + (type === 'asset' ? amount : 0),
                  liabilities: prev.liabilities + (type === 'liability' ? amount : 0),
                });
              }, new Map<string, { assets: number; liabilities: number }>())
              .entries(),
          ]
            .sort(([a], [b]) => a.localeCompare(b))
            .slice(-limit)
            .map(([period, { assets, liabilities }]) => ({
              period,
              assets,
              liabilities,
              net_worth: assets - liabilities,
            }));
    
          const aggFlow = [
            ...flowRows
              .reduce((map, { period, type, amount }) => {
                const prev = map.get(period) ?? { income: 0, expenses: 0 };
                return map.set(period, {
                  income: prev.income + (type === 'income' ? amount : 0),
                  expenses: prev.expenses + (type === 'expense' ? amount : 0),
                });
              }, new Map<string, { income: number; expenses: number }>())
              .entries(),
          ]
            .sort(([a], [b]) => a.localeCompare(b))
            .slice(-limit)
            .map(([period, { income, expenses }]) => ({
              period,
              income,
              expenses,
              net_flow: income - expenses,
              savings_rate: income > 0 ? ((income - expenses) / income) * 100 : null,
            }));
    
          return ok({ balance: aggBal, flow: aggFlow });
        },
      );
    }
  • Imports the flowEntries table and getDb helper used by report_flow to query the database.
    import { balanceEntries, flowEntries, getDb } from '../db.ts';
    import { ok } from '../helpers.ts';
Behavior2/5

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

No annotations are provided, so the description must disclose behavioral traits. It only states what is returned, not side effects, data sources, or read-only nature. For a read-like aggregation tool, this is minimal but non-contradictory.

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

Conciseness5/5

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

Two sentences: first states action, second states output. No filler. Front-loaded with key info. Every word earns its place.

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

Completeness4/5

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

Given one parameter and no output schema, the description explains return values (income, expenses, net flow, savings rate) and ordering (sorted by period). Missing details like data sources or error conditions, but sufficient for a simple aggregation tool.

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

Parameters3/5

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

The schema has 100% coverage for the single 'limit' parameter, including min, max, default, and description. The tool description adds no additional meaning beyond the schema, so baseline 3 applies.

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

Purpose5/5

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

The description clearly states the tool aggregates cash flow entries by period and returns specific metrics (monthly income, expenses, net flow, savings rate). This distinguishes it from siblings like 'show_flow' (likely raw entries) and 'report_balance' (balance-focused).

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

Usage Guidelines3/5

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

The description implies usage for aggregated cash flow reports but does not explicitly state when to use this tool versus alternatives like 'show_flow' or 'report_combined'. No guidance on exclusions or prerequisites.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/evan-moon/firma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server