Skip to main content
Glama
kimagure-dd

@kimagure-dd/xirr-mcp

by kimagure-dd

calculate_xirr

Calculate XIRR from cash flows and portfolio valuation to get annualized return, monthly return, total gain, and convergence status for investments with irregular contributions and withdrawals.

Instructions

Calculate XIRR (Extended Internal Rate of Return) from a series of dated cash flows and a current portfolio valuation. Returns annualized rate, monthly rate, total gain, and convergence status. Useful for measuring actual investment performance accounting for irregular contributions and withdrawals.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cashFlowsYesList of cash flows. Use positive amounts for investments (buy), negative for withdrawals (sell).
currentValueYesCurrent valuation of the portfolio at valuationDate.
valuationDateYesDate of the current valuation in YYYY-MM-DD format.

Implementation Reference

  • Core XIRR calculation function. Takes cash flows (with dates), current portfolio value, and valuation date. Computes the monthly rate via Newton's method (falling back to bisection), then derives annual rate, total investment, total gain, and gain rate. Returns all results plus convergence status.
    export function calculateXirr(input: XirrInput): XirrResult {
      const { cashFlows, currentValue, valuationDate } = input;
    
      if (cashFlows.length === 0) {
        throw new Error('cashFlows must not be empty');
      }
    
      const baseDate = cashFlows[0].date;
    
      const amounts = cashFlows.map((cf) => cf.amount);
      amounts.push(-currentValue);
    
      const months = cashFlows.map((cf) => toElapsedMonths(baseDate, cf.date));
      months.push(toElapsedMonths(baseDate, valuationDate));
    
      const solved =
        solveXirrNewton(amounts, months) ?? solveXirrBisection(amounts, months);
      const converged = solved !== null;
      const monthlyRate = solved ?? 0;
    
      const annualRate = (Math.pow(1 + monthlyRate, 12) - 1) * 100;
      const totalInvestment = cashFlows.reduce((sum, cf) => sum + cf.amount, 0);
      const totalGain = currentValue - totalInvestment;
      const gainRate =
        totalInvestment !== 0 ? (totalGain / totalInvestment) * 100 : 0;
    
      return {
        monthlyRate: monthlyRate * 100,
        annualRate,
        totalInvestment,
        totalGain,
        gainRate,
        converged,
      };
    }
  • Helper functions computing the XIRR net present value (NPV) and its derivative with respect to the monthly rate, used by Newton's method.
    function xirrNpv(
      rate: number,
      amounts: number[],
      elapsedMonths: number[],
    ): number {
      let npv = 0;
      for (let i = 0; i < amounts.length; i++) {
        npv += amounts[i] / Math.pow(1 + rate, elapsedMonths[i]);
      }
      return npv;
    }
    
    function xirrNpvDerivative(
      rate: number,
      amounts: number[],
      elapsedMonths: number[],
    ): number {
      let derivative = 0;
      for (let i = 0; i < amounts.length; i++) {
        derivative +=
          (-elapsedMonths[i] * amounts[i]) /
          Math.pow(1 + rate, elapsedMonths[i] + 1);
      }
      return derivative;
    }
  • Two root-finding solvers: Newton's method (up to 100 iterations, starting at rate=0.01) with bisection fallback (up to 200 iterations, bounds -0.99 to 1.0). Returns null if Newton diverges.
    function solveXirrNewton(
      amounts: number[],
      elapsedMonths: number[],
    ): number | null {
      let rate = 0.01;
      for (let i = 0; i < NEWTON_MAX_ITERATIONS; i++) {
        const npv = xirrNpv(rate, amounts, elapsedMonths);
        if (Math.abs(npv) < CONVERGENCE_THRESHOLD) return rate;
        const derivative = xirrNpvDerivative(rate, amounts, elapsedMonths);
        if (derivative === 0) return null;
        rate = rate - npv / derivative;
      }
      return null;
    }
    
    function solveXirrBisection(
      amounts: number[],
      elapsedMonths: number[],
    ): number | null {
      let low = BISECTION_LOWER_BOUND;
      let high = BISECTION_UPPER_BOUND;
      for (let i = 0; i < BISECTION_MAX_ITERATIONS; i++) {
        const mid = (low + high) / 2;
        const npv = xirrNpv(mid, amounts, elapsedMonths);
        if (Math.abs(npv) < CONVERGENCE_THRESHOLD) return mid;
        if (npv * xirrNpv(low, amounts, elapsedMonths) < 0) {
          high = mid;
        } else {
          low = mid;
        }
      }
      return (low + high) / 2;
    }
  • TypeScript interfaces defining the input (CashFlow, XirrInput) and output (XirrResult) schemas for the calculateXirr function.
    export interface CashFlow {
      date: Date;
      amount: number;
    }
    
    export interface XirrInput {
      cashFlows: CashFlow[];
      currentValue: number;
      valuationDate: Date;
    }
    
    export interface XirrResult {
      monthlyRate: number;
      annualRate: number;
      totalInvestment: number;
      totalGain: number;
      gainRate: number;
      converged: boolean;
    }
  • src/index.ts:132-231 (registration)
    Tool registration in the MCP server. The tool named 'calculate_xirr' is registered with its JSON schema via ListToolsRequestSchema, and dispatched via CallToolRequestSchema to handleCalculateXirr.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'calculate_xirr',
          description:
            'Calculate XIRR (Extended Internal Rate of Return) from a series of dated cash flows and a current portfolio valuation. Returns annualized rate, monthly rate, total gain, and convergence status. Useful for measuring actual investment performance accounting for irregular contributions and withdrawals.',
          inputSchema: {
            type: 'object',
            properties: {
              cashFlows: {
                type: 'array',
                description:
                  'List of cash flows. Use positive amounts for investments (buy), negative for withdrawals (sell).',
                items: {
                  type: 'object',
                  properties: {
                    date: {
                      type: 'string',
                      pattern: '^\\d{4}-\\d{2}-\\d{2}$',
                      description: 'Date of the cash flow in YYYY-MM-DD format.',
                    },
                    amount: {
                      type: 'number',
                      description:
                        'Cash flow amount. Positive = invested, Negative = withdrawn.',
                    },
                  },
                  required: ['date', 'amount'],
                },
                minItems: 1,
              },
              currentValue: {
                type: 'number',
                description:
                  'Current valuation of the portfolio at valuationDate.',
              },
              valuationDate: {
                type: 'string',
                pattern: '^\\d{4}-\\d{2}-\\d{2}$',
                description: 'Date of the current valuation in YYYY-MM-DD format.',
              },
            },
            required: ['cashFlows', 'currentValue', 'valuationDate'],
          },
        },
        {
          name: 'parse_rakuten_csv',
          description:
            'Parse a Rakuten Securities transaction CSV (取引履歴) and convert it into a normalized cash flow list. Handles 買付/売却 transaction types automatically. The output can be fed directly into calculate_xirr.',
          inputSchema: {
            type: 'object',
            properties: {
              csvContent: {
                type: 'string',
                description:
                  'Raw CSV text exported from Rakuten Securities. Note: Rakuten exports are typically Shift_JIS encoded; decode to UTF-8 before passing.',
              },
            },
            required: ['csvContent'],
          },
        },
      ],
    }));
    
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
    
      try {
        let text: string;
        switch (name) {
          case 'calculate_xirr':
            text = handleCalculateXirr(args);
            break;
          case 'parse_rakuten_csv':
            text = handleParseRakutenCsv(args);
            break;
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
    
        return {
          content: [
            {
              type: 'text',
              text,
            },
          ],
        };
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({ ok: false, error: message }, null, 2),
            },
          ],
          isError: true,
        };
      }
Behavior3/5

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

With no annotations, the description must disclose behavioral traits. It indicates the outputs (annualized rate, monthly rate, total gain, convergence status) but does not mention potential issues like convergence failures, input validation, or performance limitations.

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?

The description is two sentences long, front-loaded with the purpose and return values. Every word is informative and there is no redundancy.

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 the lack of output schema, the description adequately lists the return values (annualized rate, monthly rate, total gain, convergence status). It covers the essential outputs for a financial calculation 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 input schema has 100% coverage, so the description adds limited semantic value beyond what's already documented. It reiterates that positive amounts are investments, which is already in the schema, but does not provide new details.

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 action 'Calculate XIRR' and the resource 'from a series of dated cash flows and a current portfolio valuation'. It distinguishes this tool from its sibling parse_rakuten_csv, which handles CSV parsing, by focusing on financial computation.

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

Usage Guidelines4/5

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

The description explains when to use the tool ('useful for measuring actual investment performance...'), but does not explicitly mention when not to use it or provide alternatives. However, given the distinct sibling tool, no confusion arises.

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/kimagure-dd/xirr-mcp'

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