Skip to main content
Glama
by apetta

financial_calcs

Perform Time Value of Money calculations to solve for present value, future value, payments, interest rates, IRR, or NPV using known financial variables.

Instructions

Time Value of Money (TVM) calculations: solve for PV, FV, PMT, rate, IRR, or NPV.

The TVM equation has 5 variables - know 4, solve for the 5th: PV = Present Value (lump sum now) FV = Future Value (lump sum at maturity) PMT = Payment (regular periodic cash flow) N = Number of periods I/Y = Interest rate per period

Sign convention: negative = cash out (you pay), positive = cash in (you receive)

Examples:

ZERO-COUPON BOND: PV of £1000 in 10 years at 5% calculation="pv", rate=0.05, periods=10, future_value=1000 Result: £613.91

COUPON BOND: PV of £30 annual coupons + £1000 face value at 5% yield calculation="pv", rate=0.05, periods=10, payment=30, future_value=1000 Result: £845.57

RETIREMENT SAVINGS: FV with £500/month for 30 years at 7% calculation="fv", rate=0.07/12, periods=360, payment=-500, present_value=0 Result: £566,764

MORTGAGE PAYMENT: Monthly payment on £200k loan, 30 years, 4% APR calculation="pmt", rate=0.04/12, periods=360, present_value=-200000, future_value=0 Result: £954.83

INTEREST RATE: What rate grows £613.81 to £1000 in 10 years? calculation="rate", periods=10, present_value=-613.81, future_value=1000 Result: 0.05 (5%)

GROWING ANNUITY: Salary stream with 3.5% raises, discounted at 12% calculation="pv", rate=0.12, periods=25, payment=-45000, growth_rate=0.035 Result: £402,586

Input Schema

NameRequiredDescriptionDefault
contextNoOptional annotation to label this calculation (e.g., 'Bond A PV', 'Q2 revenue'). Appears in results for easy identification.
output_modeNoOutput format: full (default), compact, minimal, value, or final. See batch_execute tool for details.full
calculationYesWhat to solve for: pv, fv, pmt, rate, irr, or npv
rateNoInterest/discount rate per period (e.g., 0.05 for 5% annual)
periodsNoNumber of compounding periods
paymentNoRegular periodic payment (negative=pay out, positive=receive)
present_valueNoSingle lump sum at time 0 (negative=pay, positive=receive)
future_valueNoSingle lump sum at maturity (negative=owe, positive=receive)
cash_flowsNoSeries of cash flows for IRR/NPV (e.g., [-100, 30, 30, 130])
whenNoPayment timing: 'end' (ordinary) or 'begin' (annuity due)end
growth_rateNoPayment growth rate per period (0.0 for level annuity)

Input Schema (JSON Schema)

{ "properties": { "calculation": { "description": "What to solve for: pv, fv, pmt, rate, irr, or npv", "enum": [ "pv", "fv", "pmt", "rate", "irr", "npv" ], "type": "string" }, "cash_flows": { "anyOf": [ { "type": "string" }, { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Series of cash flows for IRR/NPV (e.g., [-100, 30, 30, 130])" }, "context": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Optional annotation to label this calculation (e.g., 'Bond A PV', 'Q2 revenue'). Appears in results for easy identification." }, "future_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Single lump sum at maturity (negative=owe, positive=receive)" }, "growth_rate": { "default": 0, "description": "Payment growth rate per period (0.0 for level annuity)", "minimum": 0, "type": "number" }, "output_mode": { "default": "full", "description": "Output format: full (default), compact, minimal, value, or final. See batch_execute tool for details.", "enum": [ "full", "compact", "minimal", "value", "final" ], "type": "string" }, "payment": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Regular periodic payment (negative=pay out, positive=receive)" }, "periods": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Number of compounding periods" }, "present_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Single lump sum at time 0 (negative=pay, positive=receive)" }, "rate": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Interest/discount rate per period (e.g., 0.05 for 5% annual)" }, "when": { "default": "end", "description": "Payment timing: 'end' (ordinary) or 'begin' (annuity due)", "enum": [ "end", "begin" ], "type": "string" } }, "required": [ "calculation" ], "type": "object" }

Implementation Reference

  • Registration of the 'financial_calcs' tool using the @mcp.tool decorator, including detailed description and annotations.
    @mcp.tool( name="financial_calcs", description="""Time Value of Money (TVM) calculations: solve for PV, FV, PMT, rate, IRR, or NPV. The TVM equation has 5 variables - know 4, solve for the 5th: PV = Present Value (lump sum now) FV = Future Value (lump sum at maturity) PMT = Payment (regular periodic cash flow) N = Number of periods I/Y = Interest rate per period Sign convention: negative = cash out (you pay), positive = cash in (you receive) Examples: ZERO-COUPON BOND: PV of £1000 in 10 years at 5% calculation="pv", rate=0.05, periods=10, future_value=1000 Result: £613.91 COUPON BOND: PV of £30 annual coupons + £1000 face value at 5% yield calculation="pv", rate=0.05, periods=10, payment=30, future_value=1000 Result: £845.57 RETIREMENT SAVINGS: FV with £500/month for 30 years at 7% calculation="fv", rate=0.07/12, periods=360, payment=-500, present_value=0 Result: £566,764 MORTGAGE PAYMENT: Monthly payment on £200k loan, 30 years, 4% APR calculation="pmt", rate=0.04/12, periods=360, present_value=-200000, future_value=0 Result: £954.83 INTEREST RATE: What rate grows £613.81 to £1000 in 10 years? calculation="rate", periods=10, present_value=-613.81, future_value=1000 Result: 0.05 (5%) GROWING ANNUITY: Salary stream with 3.5% raises, discounted at 12% calculation="pv", rate=0.12, periods=25, payment=-45000, growth_rate=0.035 Result: £402,586""", annotations=ToolAnnotations( title="Financial Calculations", readOnlyHint=True, idempotentHint=True, ), )
  • Pydantic schema definition for the financial_calcs tool parameters, including types, descriptions, and constraints.
    async def financial_calcs( calculation: Annotated[ Literal["pv", "fv", "pmt", "rate", "irr", "npv"], Field(description="What to solve for: pv, fv, pmt, rate, irr, or npv"), ], rate: Annotated[ float | None, Field(description="Interest/discount rate per period (e.g., 0.05 for 5% annual)"), ] = None, periods: Annotated[int | None, Field(description="Number of compounding periods", ge=1)] = None, payment: Annotated[ float | None, Field(description="Regular periodic payment (negative=pay out, positive=receive)"), ] = None, present_value: Annotated[ float | None, Field(description="Single lump sum at time 0 (negative=pay, positive=receive)"), ] = None, future_value: Annotated[ float | None, Field(description="Single lump sum at maturity (negative=owe, positive=receive)"), ] = None, cash_flows: Annotated[ Union[str, List[float], None], Field(description="Series of cash flows for IRR/NPV (e.g., [-100, 30, 30, 130])"), ] = None, when: Annotated[ Literal["end", "begin"], Field(description="Payment timing: 'end' (ordinary) or 'begin' (annuity due)"), ] = "end", growth_rate: Annotated[ float, Field(description="Payment growth rate per period (0.0 for level annuity)", ge=0) ] = 0.0, ) -> str:
  • Core handler logic implementing financial calculations for PV, FV, PMT, rate, IRR, NPV using numpy_financial (npf) functions, with support for growing annuities and metadata generation.
    try: # Parse stringified JSON from XML serialization if isinstance(cash_flows, str): cash_flows = cast(List[float], json.loads(cash_flows)) if calculation == "pv": # Present Value: solve for PV given FV and/or PMT if rate is None: raise ValueError("PV calculation requires rate") if periods is None: raise ValueError("PV calculation requires periods") # Ensure at least one component was provided if future_value is None and payment is None: raise ValueError("PV: provide rate + periods + (future_value AND/OR payment)") # Handle growing annuity case if growth_rate != 0.0 and payment is not None and payment != 0: # Validate growth rate if growth_rate < 0: raise ValueError("Growth rate cannot be negative") # Calculate PV of growing annuity (formula works with positive values) payment_abs = abs(payment) if abs(rate - growth_rate) < 1e-10: # Special case: rate == growth_rate pv_annuity = payment_abs * periods / (1 + rate) else: # Standard growing annuity formula growth_factor = (1 + growth_rate) / (1 + rate) pv_annuity = payment_abs * (1 - growth_factor**periods) / (rate - growth_rate) # Adjust for annuity due if when == "begin": pv_annuity *= 1 + rate # Apply sign: payment < 0 (pay out) → PV > 0 (value received) pv_annuity = pv_annuity if payment < 0 else -pv_annuity # Add PV of lump sum if present if future_value is not None and future_value != 0: pv_lumpsum = abs(future_value) / ((1 + rate) ** periods) # future_value > 0 (receive) → PV < 0 (cost) pv_lumpsum = -pv_lumpsum if future_value > 0 else pv_lumpsum pv_annuity += pv_lumpsum result = pv_annuity else: # Use numpy-financial for standard (non-growing) calculation result = npf.pv( rate, periods, float(payment) if payment is not None else 0.0, float(future_value) if future_value is not None else 0.0, # type: ignore[arg-type] when=when, ) elif calculation == "rate": # Solve for interest rate if periods is None or present_value is None: raise ValueError("Rate calculation requires periods and present_value") # Ensure we have either future_value or payment if (future_value is None or future_value == 0) and (payment is None or payment == 0): raise ValueError("Rate calculation requires either future_value or payment") # Use numpy-financial for battle-tested calculation # Note: numpy-financial handles PV=0 correctly for annuity scenarios result = npf.rate( periods, float(payment) if payment is not None else 0.0, present_value, float(future_value) if future_value is not None else 0.0, when=when, ) elif calculation == "fv": # Future Value if rate is None: raise ValueError("FV calculation requires rate") if payment is None or periods is None: raise ValueError("FV calculation requires rate, periods, and payment") # Handle growing annuity case if growth_rate != 0.0 and payment != 0: # Validate growth rate if growth_rate < 0: raise ValueError("Growth rate cannot be negative") # Calculate FV of growing annuity (formula works with positive values) payment_abs = abs(payment) if abs(rate - growth_rate) < 1e-10: # Special case: rate == growth_rate fv_annuity = payment_abs * periods * ((1 + rate) ** (periods - 1)) else: # Standard growing annuity FV formula fv_annuity = payment_abs * ( ((1 + rate) ** periods - (1 + growth_rate) ** periods) / (rate - growth_rate) ) # Adjust for annuity due if when == "begin": fv_annuity *= 1 + rate # Apply sign: payment < 0 (pay) → FV > 0 (accumulate) fv_annuity = fv_annuity if payment < 0 else -fv_annuity # Add FV of present value if present if present_value is not None and present_value != 0: fv_pv = present_value * ((1 + rate) ** periods) fv_annuity += fv_pv result = fv_annuity else: # Use numpy-financial for standard (non-growing) calculation result = npf.fv( rate, periods, payment, float(present_value) if present_value is not None else 0.0, when=when, ) elif calculation == "pmt": # Payment if rate is None: raise ValueError("PMT calculation requires rate") if present_value is None or periods is None: raise ValueError("PMT calculation requires rate, periods, and present_value") # Use numpy-financial for battle-tested calculation result = npf.pmt( rate, periods, present_value, float(future_value) if future_value is not None else 0.0, # type: ignore[arg-type] when=when, ) elif calculation == "irr": # Internal Rate of Return if cash_flows is None or len(cash_flows) < 2: raise ValueError("IRR calculation requires cash_flows with at least 2 values") # Use numpy-financial for battle-tested calculation result = npf.irr(cash_flows) elif calculation == "npv": # Net Present Value if rate is None: raise ValueError("NPV calculation requires rate") if cash_flows is None: raise ValueError("NPV calculation requires cash_flows and rate") # Use numpy-financial for battle-tested calculation # Note: npf.npv treats first value as t=0 (present), matching our convention result = npf.npv(rate, cash_flows) else: raise ValueError(f"Unknown calculation type: {calculation}") # Build metadata with all provided parameters for better context management metadata: Dict[str, Any] = {"calculation": calculation} if rate is not None: metadata["rate"] = rate if periods is not None: metadata["periods"] = periods if payment is not None: metadata["payment"] = payment if present_value is not None: metadata["present_value"] = present_value if future_value is not None: metadata["future_value"] = future_value if cash_flows is not None: metadata["cash_flows"] = cash_flows if when != "end": metadata["when"] = when if growth_rate != 0.0: metadata["growth_rate"] = growth_rate return format_result(float(result), metadata) except Exception as e: raise ValueError(f"Financial calculation failed: {str(e)}")
  • Import of financial module in tools/__init__.py that triggers the @mcp.tool registration when imported.
    from . import financial
  • Import in server.py that loads tools/__init__.py, ensuring all tools including financial_calcs are registered with the MCP server.
    from .tools import array, basic, batch, calculus, financial, linalg, statistics # noqa: E402

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/apetta/vibe-math-mcp'

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