show_txns
Retrieve a chronological list of all investment transactions including buys, sells, deposits, dividends, and taxes. Filter by ticker to view per-position history.
Instructions
Full transaction log (buys, sells, deposits, dividends, taxes) ordered by date ascending. Pass ticker to filter to one symbol — useful for "show me all my AAPL trades" or computing a per-position story. Each row has the original currency it was entered in.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ticker | No | Filter by ticker (e.g. AAPL) |
Implementation Reference
- apps/mcp/src/tools/portfolio.ts:158-174 (handler)The MCP tool handler for 'show_txns'. Queries the transactions table from SQLite via Drizzle ORM, optionally filtered by ticker, ordered by date ascending, and returns the rows via the 'ok' helper.
server.tool( 'show_txns', 'Full transaction log (buys, sells, deposits, dividends, taxes) ordered by date ascending. Pass `ticker` to filter to one symbol — useful for "show me all my AAPL trades" or computing a per-position story. Each row has the original currency it was entered in.', { ticker: z.string().optional().describe('Filter by ticker (e.g. AAPL)') }, async ({ ticker }) => { const db = getDb(); const rows = ticker ? db .select() .from(transactions) .where(eq(transactions.ticker, ticker.toUpperCase())) .orderBy(asc(transactions.date)) .all() : db.select().from(transactions).orderBy(asc(transactions.date)).all(); return ok(rows); }, ); - Input schema for show_txns: an optional ticker string parameter using Zod validation.
{ ticker: z.string().optional().describe('Filter by ticker (e.g. AAPL)') }, - apps/mcp/src/index.ts:19-19 (registration)Registration site: registerPortfolioTools is called in the MCP server entry point, which registers the 'show_txns' tool under the McpServer instance.
registerPortfolioTools(server); - apps/mcp/src/tools/portfolio.ts:23-23 (registration)The function that registers all portfolio tools (including show_txns) on the McpServer. show_txns is one of several tools registered within this function.
export function registerPortfolioTools(server: McpServer): void { - apps/cli/src/commands/txns.ts:27-129 (helper)CLI version of the show_txns logic (showTxnsCommand). Same query logic as the MCP handler but with formatted terminal output including ID, date, ticker, type, shares, price, total, and average cost tracking.
export const showTxnsCommand = async (ticker?: string, { json = false } = {}) => { const db = getDb(); const all = ticker ? db .select() .from(transactions) .where(eq(transactions.ticker, ticker.toUpperCase())) .orderBy(asc(transactions.date)) .all() : db.select().from(transactions).orderBy(asc(transactions.date)).all(); const txns = [...all].reverse(); if (txns.length === 0) { if (json) { process.stdout.write('[]\n'); return; } log.warn( ticker ? `No transactions found for ${ticker.toUpperCase()}.` : 'No transactions found.', ); return; } if (json) { process.stdout.write(`${JSON.stringify(txns, null, 2)}\n`); return; } const showTicker = !ticker; const showAvg = !!ticker; const header = [ pc.dim('ID'.padEnd(COL.ID)), pc.dim('DATE'.padEnd(COL.DATE)), ...(showTicker ? [pc.dim('TICKER'.padEnd(COL.TICKER))] : []), pc.dim('TYPE'.padEnd(COL.TYPE)), pc.dim('SHARES'.padEnd(COL.SHARES)), pc.dim('PRICE'.padEnd(COL.PRICE)), pc.dim('TOTAL'.padEnd(COL.TOTAL)), ...(showAvg ? [pc.dim('AVG COST')] : []), ].join(' '); const totalWidth = COL.ID + COL.DATE + (showTicker ? COL.TICKER + 2 : 0) + COL.TYPE + COL.SHARES + COL.PRICE + COL.TOTAL + (showAvg ? COL.AVG + 2 : 0) + 10; const divider = pc.dim('─'.repeat(totalWidth)); const ordered = showAvg ? [...txns].reverse() : txns; const { rows: rowsAsc } = ordered.reduce( ({ shares, costShares, totalCost, rows }, t) => { const colorType = TYPE_COLOR[t.type] ?? ((s: string) => s); const total = t.shares * t.price; let ns = shares, nc = costShares, nt = totalCost; if (t.type === 'buy') { ns = shares + t.shares; nc = costShares + t.shares; nt = totalCost + total; } else if (t.type === 'sell') { ns = shares - t.shares; const ratio = shares > 0 ? ns / shares : 0; nc = costShares * ratio; nt = totalCost * ratio; } else if (t.type === 'deposit' && t.price > 0) { ns = shares + t.shares; nc = costShares + t.shares; nt = totalCost + t.shares * t.price; } else if (t.type === 'deposit') { ns = shares + t.shares; } const avg = nc > 0 ? nt / nc : 0; const row = [ pc.dim(`#${t.id}`.padEnd(COL.ID)), pc.dim(t.date.padEnd(COL.DATE)), ...(showTicker ? [pc.bold(t.ticker.padEnd(COL.TICKER))] : []), colorType(t.type.padEnd(COL.TYPE)), fmt.num(t.shares).padEnd(COL.SHARES), (t.price > 0 ? fmt.price(t.price, t.currency) : pc.dim('─')).padEnd(COL.PRICE), (total > 0 ? fmt.price(total, t.currency) : pc.dim('─')).padEnd(COL.TOTAL), ...(showAvg ? [avg > 0 ? pc.bold(fmt.price(avg, t.currency)) : pc.dim('─')] : []), ].join(' '); return { shares: ns, costShares: nc, totalCost: nt, rows: [...rows, row] }; }, { shares: 0, costShares: 0, totalCost: 0, rows: [] as string[] }, ); const rows = showAvg ? rowsAsc.reverse() : rowsAsc; const title = ticker ? `Transactions · ${ticker.toUpperCase()}` : 'Transactions'; const footer = `\n${pc.dim(`${txns.length} transaction${txns.length !== 1 ? 's' : ''}`)}`; section(title, `${header}\n${divider}\n${rows.join('\n')}${footer}`); };