get_retirement_projection
Run a Monte Carlo retirement simulation to project probability of success, depletion age, and key milestones. Input current age and optional parameters for personalized results.
Instructions
Run the multi-decade Monte Carlo retirement simulator. Returns probability of success, depletion age, and summary at key milestones (retirement, age 73 for RMDs, etc.).
Caveat: scenarios live in the Tusk Ledger UI's localStorage on the device the user last edited from — they aren't accessible to this tool. So the user (or their assistant) must supply at least current_age. Other params accept sensible defaults that match the standard 4% rule scenario; pass any you know to tighten the projection. To pull a saved scenario verbatim, the user can copy it out of the Retirement page in the UI and paste the values into the assistant's prompt.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| current_age | Yes | User's current age. Required. | |
| retirement_age | No | Target retirement age (default 65). | |
| spouse_age | No | Spouse's current age. Optional — enables two-phase simulation when paired with spouse_retirement_age. | |
| spouse_retirement_age | No | Age at which the spouse retires (in spouse's years). | |
| desired_annual_income | No | Target annual spending in retirement, today's dollars (default 80000). | |
| annual_contribution | No | Annual contribution. Omit to auto-detect from last 12mo of investment-account inflows. | |
| return_rate | No | Real annual return during accumulation (default 0.06 = 6%). | |
| withdrawal_rate | No | Safe withdrawal rate (default 0.04 = the 4% rule). | |
| pension_annual | No | Annual pension income, today's dollars. | |
| ss_annual | No | Annual Social Security at the user's claim age. | |
| ss_start_age | No | Age at which to claim SS (62–70, default 67). | |
| inflation_rate | No | Long-run inflation assumption (default 0.025). |
Implementation Reference
- tuskledger_mcp/server.py:304-309 (handler)Dispatch handler: scrubs None/empty arguments, calls client.retirement_projection() with user-supplied params (at least current_age required) and returns the projection result.
if name == "get_retirement_projection": # Pass through any params the assistant supplied; the backend # validates current_age (required) and assigns sane defaults to # the rest. Scrub Nones so the URL stays clean. params = {k: v for k, v in a.items() if v not in (None, "")} return client.retirement_projection(**params) - tuskledger_mcp/server.py:205-238 (schema)Tool registration with input schema: defines the get_retirement_projection MCP tool, its description (Monte Carlo retirement simulator), and JSON Schema with required current_age field and many optional parameters.
Tool( name="get_retirement_projection", description=( "Run the multi-decade Monte Carlo retirement simulator. Returns " "probability of success, depletion age, and summary at key " "milestones (retirement, age 73 for RMDs, etc.).\n\n" "Caveat: scenarios live in the Tusk Ledger UI's localStorage on " "the device the user last edited from — they aren't accessible " "to this tool. So the user (or their assistant) must supply at " "least current_age. Other params accept sensible defaults that " "match the standard 4% rule scenario; pass any you know to " "tighten the projection. To pull a saved scenario verbatim, the " "user can copy it out of the Retirement page in the UI and " "paste the values into the assistant's prompt." ), inputSchema={ "type": "object", "required": ["current_age"], "properties": { "current_age": {"type": "integer", "description": "User's current age. Required."}, "retirement_age": {"type": "integer", "description": "Target retirement age (default 65)."}, "spouse_age": {"type": "integer", "description": "Spouse's current age. Optional — enables two-phase simulation when paired with spouse_retirement_age."}, "spouse_retirement_age": {"type": "integer", "description": "Age at which the spouse retires (in spouse's years)."}, "desired_annual_income": {"type": "number", "description": "Target annual spending in retirement, today's dollars (default 80000)."}, "annual_contribution": {"type": "number", "description": "Annual contribution. Omit to auto-detect from last 12mo of investment-account inflows."}, "return_rate": {"type": "number", "description": "Real annual return during accumulation (default 0.06 = 6%)."}, "withdrawal_rate": {"type": "number", "description": "Safe withdrawal rate (default 0.04 = the 4% rule)."}, "pension_annual": {"type": "number", "description": "Annual pension income, today's dollars."}, "ss_annual": {"type": "number", "description": "Annual Social Security at the user's claim age."}, "ss_start_age": {"type": "integer", "description": "Age at which to claim SS (62–70, default 67)."}, "inflation_rate": {"type": "number", "description": "Long-run inflation assumption (default 0.025)."}, }, "additionalProperties": False, }, - tuskledger_mcp/server.py:47-250 (registration)The tool is registered in the TOOLS list which is returned by the list_tools() handler, and dispatched in the call_tool() handler via _dispatch().
TOOLS: list[Tool] = [ Tool( name="list_accounts", description=( "List every connected account in Tusk Ledger with current " "balance, type (checking, savings, credit, investment, loan), " "and last-sync timestamp. Use this first to understand what " "accounts exist before drilling into transactions or holdings." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), Tool( name="list_stale_accounts", description=( "Return accounts whose data is older than the freshness " "threshold (a week for synced accounts, a month for manual). " "Useful when the user asks 'why is my net worth wrong?' — " "stale balances are usually the cause." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), Tool( name="query_transactions", description=( "List transactions matching optional filters. Returns the most " "recent matches first. Common filter combos:\n" " • account_id + start_date + end_date → 'all transactions in " " my checking account this month'\n" " • category='Coffee' + start_date='2026-01-01' → 'every " " coffee purchase since New Year'\n" "Defaults to no filter (returns the most recent 100 transactions " "across all accounts)." ), inputSchema={ "type": "object", "properties": { "account_id": {"type": "integer", "description": "Filter to a single account by id."}, "category": {"type": "string", "description": "Filter to a single category name (exact match)."}, "start_date": {"type": "string", "description": "ISO date YYYY-MM-DD; inclusive lower bound."}, "end_date": {"type": "string", "description": "ISO date YYYY-MM-DD; inclusive upper bound."}, "limit": {"type": "integer", "description": "Max rows to return (default 100, max 500)."}, }, "additionalProperties": False, }, ), Tool( name="search_transactions", description=( "Free-text search across transaction names, merchant names, and " "notes. Use when the user asks 'find that Whole Foods charge " "from last week' or 'when did I last pay Verizon?'. Different " "from query_transactions in that this is a fuzzy text search, " "not a structured filter." ), inputSchema={ "type": "object", "required": ["q"], "properties": { "q": {"type": "string", "description": "Search string. Matches partial words, case-insensitive."}, "limit": {"type": "integer", "description": "Max rows (default 50)."}, }, "additionalProperties": False, }, ), Tool( name="get_spending_summary", description=( "Aggregated spending totals broken down by category for a date " "range. Returns totals + per-category subtotals + counts. " "Defaults to the current calendar month if no dates given." ), inputSchema={ "type": "object", "properties": { "start_date": {"type": "string", "description": "ISO date YYYY-MM-DD."}, "end_date": {"type": "string", "description": "ISO date YYYY-MM-DD."}, "exclude_business": {"type": "boolean", "description": "Drop transactions tagged as business (default false)."}, }, "additionalProperties": False, }, ), Tool( name="get_top_merchants", description=( "Top N merchants by total spend in a date range. Returns merchant " "name, total amount, transaction count, and a sparkline of the " "monthly trend. Useful for 'who am I paying the most?'." ), inputSchema={ "type": "object", "properties": { "start_date": {"type": "string", "description": "ISO date."}, "end_date": {"type": "string", "description": "ISO date."}, "limit": {"type": "integer", "description": "How many merchants to return (default 10)."}, }, "additionalProperties": False, }, ), Tool( name="get_recurring_subscriptions", description=( "List detected recurring subscriptions: Netflix, Spotify, gym, " "etc. Returns merchant, cadence (monthly/annual/etc.), last " "amount, next expected date, and confidence. The user often " "asks 'what subscriptions do I have' — this answers it." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), Tool( name="get_upcoming_bills", description=( "Forward 30-day calendar of expected bills + paychecks with a " "running balance. Returns each event's date, amount, source " "(merchant or paycheck), and the projected account balance " "after that event. Useful for 'is my account going to dip " "before payday?'." ), inputSchema={ "type": "object", "properties": { "days": {"type": "integer", "description": "How many days forward to look (default 30)."}, }, "additionalProperties": False, }, ), Tool( name="get_net_worth", description=( "Current net worth (assets minus liabilities) plus a 12-month " "trend. Numbers are point-in-time from the last sync, not " "live-computed. Use list_stale_accounts to verify freshness." ), inputSchema={ "type": "object", "properties": { "history": {"type": "boolean", "description": "If true, return the full snapshot history instead of just latest."}, }, "additionalProperties": False, }, ), Tool( name="get_holdings", description=( "Current investment holdings across every connected brokerage " "and 401(k). Returns symbol, account, quantity, current value, " "and unrealized gain/loss per position." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), Tool( name="get_investments_summary", description=( "Roll-up of investment portfolio: total value, asset allocation " "(stocks/bonds/cash), top 5 holdings, % YTD gain. The 'how are " "my investments doing?' answer." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), Tool( name="get_retirement_projection", description=( "Run the multi-decade Monte Carlo retirement simulator. Returns " "probability of success, depletion age, and summary at key " "milestones (retirement, age 73 for RMDs, etc.).\n\n" "Caveat: scenarios live in the Tusk Ledger UI's localStorage on " "the device the user last edited from — they aren't accessible " "to this tool. So the user (or their assistant) must supply at " "least current_age. Other params accept sensible defaults that " "match the standard 4% rule scenario; pass any you know to " "tighten the projection. To pull a saved scenario verbatim, the " "user can copy it out of the Retirement page in the UI and " "paste the values into the assistant's prompt." ), inputSchema={ "type": "object", "required": ["current_age"], "properties": { "current_age": {"type": "integer", "description": "User's current age. Required."}, "retirement_age": {"type": "integer", "description": "Target retirement age (default 65)."}, "spouse_age": {"type": "integer", "description": "Spouse's current age. Optional — enables two-phase simulation when paired with spouse_retirement_age."}, "spouse_retirement_age": {"type": "integer", "description": "Age at which the spouse retires (in spouse's years)."}, "desired_annual_income": {"type": "number", "description": "Target annual spending in retirement, today's dollars (default 80000)."}, "annual_contribution": {"type": "number", "description": "Annual contribution. Omit to auto-detect from last 12mo of investment-account inflows."}, "return_rate": {"type": "number", "description": "Real annual return during accumulation (default 0.06 = 6%)."}, "withdrawal_rate": {"type": "number", "description": "Safe withdrawal rate (default 0.04 = the 4% rule)."}, "pension_annual": {"type": "number", "description": "Annual pension income, today's dollars."}, "ss_annual": {"type": "number", "description": "Annual Social Security at the user's claim age."}, "ss_start_age": {"type": "integer", "description": "Age at which to claim SS (62–70, default 67)."}, "inflation_rate": {"type": "number", "description": "Long-run inflation assumption (default 0.025)."}, }, "additionalProperties": False, }, ), Tool( name="run_sync", description=( "Trigger a Plaid sync across all connected items. Same as " "clicking 'Sync Now' in the UI. Returns a summary of what was " "fetched (accounts updated, transactions added). Safe to call " "freely — Plaid dedupes." ), inputSchema={"type": "object", "properties": {}, "additionalProperties": False}, ), ] - tuskledger_mcp/client.py:169-170 (helper)Client helper method: sends a GET request to /api/analytics/retirement-projection on the Tusk Ledger backend, passing through all user-supplied parameters as query params.
def retirement_projection(self, **params) -> Any: return self._request("GET", "/api/analytics/retirement-projection", params=params)