API Reference
Hosted MCP server at https://mcp.digitalcalculator.info/mcp (Streamable HTTP, POST, no auth required — optional Bearer tier) · contract 1.0.0 · 9 anonymous tools — 4 scenario tools, 4 single-quantity calculator tools, and money_flow_map — plus prompts, reference-data resources, and per-response provenance. Hosted-API terms: see LICENSE-API.md (acceptable use, rate limits, no warranty). One additional tool, generate_report, is available to Bearer-tier sessions only.
What changed at 1.0
Contract 1.0.0 consolidated the former granular surface into four scenario tools (plan_retirement_income, evaluate_roth_conversion, check_contribution_eligibility, project_growth), kept the four highest-traffic single-quantity calculators unchanged in purpose, and added prompts, statutory reference-data resources, and the provenance envelope fields (rule_year, sources[]). Every retired granular tool remains reachable indefinitely via its legacy REST alias with its original wire vocabulary. See the changelog for the release entry.
Calling the server
The server speaks the MCP Streamable HTTP transport: JSON-RPC 2.0 over HTTP POST. Supported methods: initialize, server/discover, ping, tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get. A tool invocation looks like:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "mortgage_monthly_payment",
"arguments": { "principal": 300000, "annualRatePercent": 6.5, "termYears": 30 }
}
}Successful calls return a JSON-RPC envelope whose result.content[0].text contains the ToolResult JSON described below. Tool failures set result.isError: true with the ToolError JSON in the same content slot.
Protocol versions (2026-07-28 support)
Supported protocol revisions: 2026-07-28, 2025-11-25, 2025-06-18 (default 2026-07-28). Two handshakes are served from the same stateless code path:
initialize— the classic session-oriented handshake, kept working unchanged for older hosts. Carries the requested version inparams.protocolVersion.server/discover— the 2026-07-28 stateless discovery handshake: noinitializednotification expected, no session header issued or required. The requested version may also arrive viaparams._meta.protocolVersionon any request (the session-header-free carrier).
Because this server is deterministic and its discovery surfaces change only on deploy, static responses (server/discover, tools/list, resources/list, prompts/list, and every resources/read body) advertise their own cacheability via _meta: a ttlMs of 24 hours with cacheScope: "public". Clients that honor the advertisement can skip repeat discovery round-trips entirely.
REST companion
A REST API at https://api.digitalcalculator.info/v1/tools/{alias}/calculate runs the exact same engines and returns the same response envelope without the JSON-RPC wrapper. The four scenario tools have kebab-case aliases speaking the canonical vocabulary (plan-retirement-income, evaluate-roth-conversion, check-contribution-eligibility, project-growth), and every pre-1.0 alias keeps working — see Legacy REST aliases. (The earlier single-endpoint mortgage POC at /api/calculate/mortgage is legacy — do not build new integrations against it.)
Envelope shapes
ToolResult<T> (success)
interface ToolResult<T> {
result: T; // Tool-specific structured object
disclaimer: string; // Canonical YMYL disclaimer (dc://disclaimers/ymyl)
methodology: {
url: string; // Absolute URL to the primary methodology page
version: string; // ISO date of last data verification
};
rule_year: number; // Statutory year the computation used (provenance)
sources: string[]; // dc:// resource URIs the computation drew on
calculatedAt: string; // ISO-8601 UTC timestamp
engineVersion: string; // SemVer of engine that produced result
}Provenance fields (contract 1.0)
Every success envelope carries rule_year — the statutory year the computation used (currently 2026, the only supported rule year; passing any other value is rejected rather than silently computed against the wrong tables) — and sources[], the dc:// reference-data resource URIs the computation drew on (IRS limits, the Uniform Lifetime Table, SSA bend points, the methodology manifest). Scenario tools cite multiple sources; each URI is readable via resources/read. The provenance block is what a compliance reviewer audits — it is why an agent can put these numbers in front of a client with a citation trail.
Each cited source is also emitted as an MCP resource_link content block, appended after the text block in the content[] array of every successful tools/call. So provenance is not just an asserted string — a host can fetch the citation directly. The sources[] string array inside structuredContent is unchanged and remains the canonical field; the links are an additional rendering of the same facts, and a client that ignores resource_link sees exactly the response it saw before.
Applied-default disclosure (assumptions)
plan_retirement_income and project_growth accept optional inputs that carry a default — employer match, salary growth, catch-up eligibility, years worked, life expectancy, annual contribution, compounding frequency. When you omit one, the applied default is echoed in an assumptions object on the result, keyed by field name:
"assumptions": {
"annual_salary_growth_percent": {
"value": 0,
"defaulted": true,
"note": "No salary growth was provided, so salary was held flat for the whole projection. A real raise history would raise the projected balance."
}
}An empty object means every value used was supplied by the caller — the block is always present, so a client never has to branch on its existence. For plan_retirement_income the disclosure is scoped to the modules you actually selected: an RMD-only call assumes nothing and returns {}. Present these to an end user as assumptions rather than as their own figures.
Scope, stated plainly: this reports defaults the server applied. It cannot detect a rate an agent invented and passed in — annual_return_percent: 7 arrives indistinguishable from a number the user stated. Required rate inputs (annual_return_percent, annual_rate_percent) have no default for exactly that reason: they must come from the user, or be proposed to them explicitly first.
Monetary precision (round-to-cents)
Every USD-denominated output field is a JSON number rounded to the nearest cent (2 decimal places) — not full floating-point precision. Non-monetary fields (rates, ratios, counts, months, ages, IRS table factors) keep their natural precision.
ToolError (failure)
interface ToolError {
error: {
code: "INPUT_VALIDATION" | "BUSINESS_RULE" | "INTERNAL"
| "RATE_LIMIT" | "UNAUTHORIZED";
message: string; // Human-readable
field?: string; // Input field that failed (validation only)
retriable: boolean; // Only RATE_LIMIT is retriable
};
}Status mapping
- 200 + ToolResult — success
- 200 +
isError: true— tool-level error (INPUT_VALIDATION,BUSINESS_RULE) per the MCP spec - JSON-RPC error object — malformed request, unknown method (protocol-level errors)
- 401 —
UNAUTHORIZED(retriable: false) on the REST route when anAuthorization: Bearercredential is present but invalid, revoked, or expired. Sending noAuthorizationheader is not an error — that is the anonymous tier. The Streamable HTTP route returns the equivalent 401 in its own{"error":"invalid_token","message":...}shape - 429 —
RATE_LIMIT(per-caller allowance or shared endpoint throttle) - 500 —
INTERNAL(shouldn't happen in production; report it if you see one)
All error payloads are single-envelope {"error":{"code":...}} — never double-nested.
Tool naming convention
Tool names are snake_case, in a deliberate two-class grammar. Scenario tools are verb-first (plan_, evaluate_, check_, project_): the verb telegraphs multi-part, decision-shaped output. Single-quantity calculators are domain-first (mortgage_monthly_payment, paycheck_net_pay): the leading domain keyword is what a model matches against the user's own words. The legacy dot-notation names from the v0.2.x stdio package (dc.calculator.mortgage.monthlyPayment) were retired at the hosted-server cutover — dot-notation tool names break Claude Connectors' tool-name validation.
Shared vocabulary
The same concept has the same field name in every tool schema on the 1.0 surface:
| Concept | Canonical field / convention |
|---|---|
| Statutory year | rule_year — integer; optional input defaulting to the current rule year; always present in output |
| Person's age basis | birth_year (not age) — SECURE 2.0 cohorts are birth-year-based and unambiguous mid-year |
| Filing status | one filing_status enum: single · married_filing_jointly · married_filing_separately · head_of_household |
| Account type | one kind enum: 401k · trad_ira · roth_ira · hsa |
| Rates | whole-number percents (e.g., 6.5 means 6.5%) |
| Money | USD numbers; balance, annual_contribution, annual_salary vocabulary reused across tools |
A vocabulary-contract unit test diffs every field name in every schema against this table in CI — drift fails the build. Legacy REST aliases deliberately keep their pre-1.0 field spellings verbatim.
Tool index (8 calculator tools; see also money_flow_map)
| Tool | What it computes | Key params |
|---|---|---|
plan_retirement_income | Linked 401(k) accumulation + Social Security estimate + SECURE 2.0 RMD picture for one person, one call | include[], birth_year, balance, annual_salary, retirement_age, claim_age |
evaluate_roth_conversion | Federal tax impact of a single Roth conversion using rule-year brackets, with break-even framing | conversion_amount, birth_year, filing_status, current_taxable_income |
check_contribution_eligibility | Contribution limit + eligibility for a 401(k), Traditional IRA, Roth IRA, or HSA with phase-outs applied | kind, birth_year, filing_status, magi, coverage_tier |
project_growth | Nominal and inflation-adjusted growth of a balance with contributions at a chosen compounding frequency | balance, annual_contribution, annual_rate_percent, years, inflation_rate_percent |
mortgage_monthly_payment | Monthly P&I, total interest, total paid for a fixed-rate mortgage | principal, annualRatePercent, termYears |
loan_monthly_payment | Monthly payment and total interest for any fixed-rate amortizing loan | loanAmount, annualRatePercent, termYears |
paycheck_net_pay | Net take-home pay per paycheck: IRS 2026 Percentage Method + FICA + state-aware estimate | grossAnnualSalary, payFrequency, filing_status, state |
emergency_fund_recommendation | Personalized emergency-fund target and savings timeline by household situation | monthlyExpenses, employmentType, incomeEarners, dependents |
plan_retirement_income
Plan retirement income for one person in one linked call: 401(k) accumulation to a target retirement age, the Social Security benefit estimate at a chosen claim age, and the SECURE 2.0 required-minimum-distribution picture — all sharing a single birth_year, annual_salary, and statutory rule_year, so the pieces are mutually consistent. Select modules with include[]; each module only requires its own inputs, so an RMD-only question needs just birth_year + balance. Pre-RMD callers get applies: false with their SECURE 2.0 RMD start age and first RMD year ("when do RMDs start?") instead of an error. Absorbs the former retirement_401k_projection, social_security_estimated_benefit, and rmd_distribution_amount tools.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
include | array of enum | yes | accumulation · social_security · rmd | Modules to compute (1–3, duplicates ignored) |
birth_year | integer | yes | 1900 – 2100 | Single age basis for every module (1943–2004 when social_security is selected) |
rule_year | integer | no | 2026 | Statutory year; defaults to 2026, always echoed in the result |
balance | number (USD) | conditional | 0 – 100,000,000 | Required by accumulation (current 401(k) balance) and rmd (prior year-end balance) |
annual_salary | number (USD) | conditional | 0 – 100,000,000 | Required by accumulation and social_security (max 1,000,000 for that module) |
contribution_percent | number | conditional | 0 – 100 | Employee 401(k) contribution %; required by accumulation |
employer_match_percent | number | no | 0 – 100 | Match % (50 = $0.50 per $1); default 0 |
employer_match_limit_percent | number | no | 0 – 100 | Cap on % matched; default 0 |
annual_salary_growth_percent | number | no | 0 – 20 | Salary growth %; default 0 |
annual_return_percent | number | conditional | 0 – 20 | Investment return %; required by accumulation |
retirement_age | integer | conditional | 18 – 100 | Required by accumulation; must exceed the rule-year age derived from birth_year |
catch_up_enabled | boolean | no | — | Apply IRS catch-up in eligible years; default false (explicit opt-in) |
claim_age | number | conditional | 62 – 70 | Required by social_security |
years_worked | integer | no | 0 – 50 | Default 35 (SSA averaging window) |
life_expectancy | number | no | 62 – 120 | Default 85; drives the lifetime benefit projection |
spouse_birth_year | integer | no | 1900 – 2100 | Optional for rmd; a sole-beneficiary spouse >10 years younger triggers the jointLifeTableMayApply warning |
is_spouse_sole_beneficiary | boolean | no | — | Optional for rmd; see above |
Conditional requirements: accumulation needs balance, annual_salary, contribution_percent, annual_return_percent, retirement_age; social_security needs annual_salary, claim_age; rmd needs only balance. Missing conditional inputs return INPUT_VALIDATION naming the field and the module that needs it.
Output payload
Only the selected modules appear under modules; every module is computed from the same rule_year and birth_year. Example (all three modules; born 1968, $110,000 salary, $500,000 balance, 10% contribution, 6% return, retire at 62, claim at 62):
{
"rule_year": 2026,
"birth_year": 1968,
"age_in_rule_year": 58,
"modules": {
"accumulation": {
"futureBalance": 679359.26,
"totalContributions": 44000.00,
"employerContribTotal": 0.00,
"growthEarned": 135359.26,
"yearByYear": [ { "year": 1, "age": 59, "employeeContribution": 11000.00, "employerMatch": 0.00, "growth": 30000.00, "endingBalance": 541000.00 } ],
"catchUpNote": "This projection EXCLUDES catch-up contributions (catchUpEnabled is false)..."
},
"social_security": {
"monthlyBenefitAtFRA": 2652.40,
"adjustedMonthlyBenefit": 1856.68,
"lifetimeBenefitProjection": 512444,
"fraAge": 67,
"eligibilityYear": 2030,
"bendPointsEstimated": true
},
"rmd": {
"applies": false,
"rmd_start_age": 75,
"first_rmd_year": 2043,
"note": "No RMD is required for rule year 2026: you are 58 and your SECURE 2.0 required-beginning age is 75 (born 1968)..."
}
}
}When the rmd module applies (owner at or past the required-beginning age), it carries the full RMD payload instead: rmdAmount, distributionPeriod, lifeExpectancyFactor, tableUsed, penaltyIfMissed (25% default, 10% corrected per SECURE 2.0 §302), jointLifeTableMayApply, and spouseBeneficiaryNote.
YMYL caveat (Social Security module)
The Social Security estimate uses a simplified AIME approximation, not the official 35-highest-indexed-earnings calculation that requires a full SSA earnings history. For an authoritative estimate, direct users to ssa.gov/myaccount.
evaluate_roth_conversion
Evaluate the federal income tax impact of a single Roth conversion using rule-year IRS brackets. Returns the incremental tax owed, the top marginal bracket the conversion pushes you into, the effective rate on the conversion, a Roth 5-year-rule flag (age derived from birth_year), and optional break-even years when you supply your expected retirement marginal rate. This is the renamed roth_conversion_tax_impact — same engine, canonical vocabulary.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
conversion_amount | number (USD) | yes | 0 – 10,000,000 | Amount converted to Roth |
birth_year | integer | yes | 1900 – 2100 | Drives the Roth 5-year-rule flag via the rule-year age (18–100) |
filing_status | enum | yes | single · married_filing_jointly · married_filing_separately · head_of_household | Canonical shared enum |
current_taxable_income | number (USD) | yes | 0 – 10,000,000 | Taxable income BEFORE adding the conversion |
retirement_marginal_rate_percent | number | no | 0 – 50 | Expected retirement marginal rate; enables break-even calc |
expected_return_percent | number | no | 0 – 20 | Annual return on converted balance; default 7 |
rule_year | integer | no | 2026 | Statutory year; defaults to 2026 |
Output payload
Example ($50,000 conversion, married filing jointly, $110,000 taxable income, born 1968):
{
"rule_year": 2026,
"taxOwed": 11000.00,
"marginalRateOnConversion": 0.22,
"effectiveRateOnConversion": 0.22,
"fiveYearRuleApplies": true,
"breakEvenYears": null,
"breakEvenNote": "breakEvenYears requires retirementMarginalRatePercent; provide it to compute the break-even horizon...",
"meta": { "tableYear": 2026, "source": "IRS Rev. Proc. 2025-32" }
}Scope limits
Single-conversion only (multi-year conversion laddering is not modeled). State tax and IRMAA tier impact are NOT modeled — the caller is responsible for those. breakEvenYears uses the site calculator's simplified log-ratio model, explained in breakEvenNote on every result.
check_contribution_eligibility
Check how much you can contribute to a retirement or health savings account for the rule year, with phase-outs and eligibility applied. Pick the account with kind: 401k (IRC §402(g) elective-deferral limit incl. the SECURE 2.0 ages 60–63 super catch-up), trad_ira (contribution limit plus the IRC §219(g) deduction-status signal driven by workplace coverage), roth_ira (MAGI phase-out per IRC §408A(c)(3)), or hsa (Rev. Proc. 2025-19 limits by coverage tier, age 55+ catch-up, employer-contribution offset). Absorbs the former ira_contribution_limit and hsa_contribution_limit tools; pairs with the dc://irs/limits resource.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
kind | enum | yes | 401k · trad_ira · roth_ira · hsa | Account type (canonical shared enum) |
birth_year | integer | yes | 1900 – 2100 | Catch-up eligibility (50+ IRA/401k, 55+ HSA, 60–63 super catch-up) derives from the rule-year age (18–100) |
rule_year | integer | no | 2026 | Statutory year; defaults to 2026 |
filing_status | enum | conditional | canonical shared enum | Required when kind is trad_ira or roth_ira |
magi | number (USD) | conditional | 0 – 10,000,000 | Required when kind is trad_ira or roth_ira; drives the phase-outs |
workplace_coverage | boolean | no | — | trad_ira: active participant in a workplace plan? Omit and the deduction status assumes no coverage, flagged indicative-only |
spouse_workplace_coverage | boolean | no | — | trad_ira, married filing jointly: triggers the higher spousal phase-out band when the filer is not covered |
coverage_tier | enum | conditional | self-only · family | Required when kind is hsa |
employer_contribution | number (USD) | no | 0 – 50,000 | hsa: reduces employee payroll-deduction room dollar-for-dollar; default 0 |
estimated_marginal_rate_percent | number | no | 0 – 50 | hsa: returns the payroll-deduction tax-savings estimate |
Output payload
Every result carries the linked fields rule_year, kind, birth_year, age_in_rule_year, plus the underlying engine's fields for the selected kind. Example (kind: "401k", born 1964 — the ages 60–63 super-catch-up window):
{
"rule_year": 2026,
"kind": "401k",
"birth_year": 1964,
"age_in_rule_year": 62,
"contributionLimit": 24500.00,
"catchUpEligible": true,
"superCatchUpApplies": true,
"catchUpAmount": 11250.00,
"totalContributionLimit": 35750.00,
"meta": { "tableYear": 2026, "source": "IRS Notice 2025-67; SECURE 2.0 Act §109 (ages 60-63 super catch-up)" }
}IRA kinds return eligibleContribution, traditionalCap, rothCap, catchUp, phaseOutReduction, catchUpEligible, traditionalDeductionStatus, and traditionalDeductionNote; the HSA kind returns contributionLimit, catchUpAmount, totalContributionLimit, employerContribution, employeeMaxRemaining, payrollDeductionTaxSavings, employerContributionExceedsLimit, and excessContribution (with an excessContributionNote citing the IRC §4973 6% excise tax when the employer contribution alone exceeds the limit).
project_growth
Project the growth of a balance with optional yearly contributions, a chosen compounding frequency, and both nominal and inflation-adjusted (real) outputs in one envelope. Contributions are spread monthly (annual_contribution / 12); compounding_frequency defaults to monthly. Provide inflation_rate_percent to also get real_future_value — the projected balance deflated into today's dollars. Absorbs the former compound_interest_future_value, savings_future_balance, and inflation_adjusted_value tools.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
balance | number (USD) | yes | 0 – 100,000,000 | Starting balance (principal) |
annual_contribution | number (USD) | no | 0 – 12,000,000 | Yearly contribution, spread evenly across the year; default 0 |
annual_rate_percent | number | yes | 0 – 50 | Annual growth rate as whole-number percent |
years | integer | yes | 1 – 100 | Projection horizon in whole years |
compounding_frequency | enum | no | annually · semiannually · quarterly · monthly · daily | Default monthly |
inflation_rate_percent | number | no | 0 – 20 | When provided, the result also carries real_future_value |
Output payload
Example ($10,000 starting balance, $6,000/year, 7%, 30 years, monthly compounding, 3% inflation):
{
"future_value": 691150.47,
"total_contributions": 190000.00,
"interest_earned": 501150.47,
"years": 30,
"compounding_frequency": "monthly",
"annual_contribution": 6000.00,
"inflation_rate_percent": 3,
"real_future_value": 284744.84,
"real_note": "real_future_value expresses the projected balance in rule-year (today's) dollars, deflated at 3% annual inflation over 30 years. future_value is the nominal figure.",
"assumptions": {}
}The example above supplies every optional input, so assumptions is empty. Omit annual_contribution or compounding_frequency and each appears there with the default that was applied — see applied-default disclosure.
mortgage_monthly_payment
Compute the monthly principal & interest payment, total interest paid, and total amount paid over the life of a fixed-rate mortgage. loan_monthly_payment is a parallel principal-&-interest calculator; both use the same amortization formula and return identical numbers for the same principal/rate/term. Neither models down payment, PMI, property tax, or homeowners insurance — pick by vocabulary (principal here vs loanAmount there).
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
principal | number (USD) | yes | 0 – 100,000,000 | Loan principal |
annualRatePercent | number | yes | 0 – 30 | Annual rate as whole-number percent (e.g., 6.5) |
termYears | integer | yes | 1 – 50 | Loan term in whole years |
Output payload
{
"monthlyPayment": 1896.20,
"totalInterest": 382633.47,
"totalPaid": 682633.47
}Scope note
This tool computes P&I only. PITI (with property tax, insurance, HOA, PMI) is reserved for a future additive tool. For a fixed-rate non-mortgage loan, use loan_monthly_payment.
loan_monthly_payment
Compute the monthly payment, total interest, and total paid for a fixed-rate amortizing loan (personal, auto, student, etc.) via the standard amortization formula. Zero-interest loans are handled as simple principal division. Mirrors the site's loan calculator engine exactly.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
loanAmount | number (USD) | yes | 0 – 100,000,000 | Loan principal |
annualRatePercent | number | yes | 0 – 30 | Annual rate as whole-number percent; 0 for interest-free |
termYears | integer | yes | 1 – 50 | Loan term in whole years |
Output payload
{
"loanAmount": 25000.00,
"annualRatePercent": 8.5,
"termYears": 5,
"numPayments": 60,
"monthlyRate": 0.00708333,
"monthlyPayment": 512.91,
"totalPaid": 30774.80,
"totalInterest": 5774.80
}paycheck_net_pay
Estimate net (take-home) pay per paycheck using the IRS 2026 federal Percentage Method, FICA (Social Security 6.2% + Medicare 1.45% + Additional Medicare 0.9% above thresholds), and a state-aware tax estimate. Pre-tax deductions split by FICA treatment: preTax401kAnnual reduces income-tax wages only (401(k) stays in the FICA wage base), while preTaxCafeteriaAnnual (Section 125: employer health premiums, FSA, HSA) also reduces FICA wages.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
grossAnnualSalary | number (USD) | yes | 0 – 10,000,000 | Gross annual salary |
payFrequency | enum | yes | weekly · biweekly · semimonthly · monthly · quarterly · annual | Pay frequency |
filing_status | enum | yes | single · married_filing_jointly · married_filing_separately · head_of_household | Federal filing status (canonical shared enum) |
state | string | yes | 2-letter code or DC | US state code, case-insensitive; unknown codes rejected with INPUT_VALIDATION |
dependents | integer | no | 0 – 20 | Form W-4 line 3; applies the Step 3 dependent credit of $2,000 per dependent against annual federal withholding (floored at $0). Default 0 |
preTax401kAnnual | number (USD) | no | 0 – 1,000,000 | Traditional 401(k)/403(b) pre-tax contributions; reduces federal + state taxable wages ONLY (still FICA-taxed). Default 0 |
preTaxCafeteriaAnnual | number (USD) | no | 0 – 1,000,000 | Section 125 cafeteria-plan deductions; reduces taxable wages AND the FICA wage base. Default 0 |
preTaxDeductionsAnnual | number (USD) | no | 0 – 1,000,000 | DEPRECATED — prefer the two split fields above; treated as 401(k)-type (does not reduce FICA wages). Default 0; omit it in new integrations |
postTaxDeductionsAnnual | number (USD) | no | 0 – 1,000,000 | Roth contributions, garnishments, etc. Default 0 |
Output payload
Example ($80,000 gross, biweekly, single, Colorado, no deductions):
{
"grossPerPaycheck": 3076.92,
"federalTax": 337.31,
"ficaTax": 235.38,
"stateTax": 108.14,
"netPay": 2396.09,
"payPeriodsPerYear": 26,
"federalTaxAnnual": 8770.00,
"federalTaxBeforeDependentCreditAnnual": 8770.00,
"dependentCreditAnnual": 0.00,
"ficaTaxAnnual": 6120.00,
"stateTaxAnnual": 2811.60,
"netPayAnnual": 62298.40,
"noStateIncomeTax": false,
"stateTaxSource": "Tax Foundation 2026 State Individual Income Tax Rates",
"stateTaxNotes": "Flat 4.4% (2026 estimate post-TABOR refund).",
"stateEffectiveRate": 0.035145
}Scope note (state tax)
State withholding uses a simplified estimate — $0 for the 9 no-income-tax states (AK, FL, NV, NH, SD, TN, TX, WA, WY); Tax Foundation 2026 effective rates by income tier for all other states + DC. Full per-state brackets, SDI, and local-tax piggybacks (NYC, PA EIT, etc.) are NOT modeled. Direct callers needing precise state math to the full paycheck calculator.
emergency_fund_recommendation
Compute a personalized emergency-fund target and savings timeline. The recommended coverage months come from the household employment situation (employed dual-income with no dependents = 3 months; standard employed = 6; self-employed/freelance = 9; between jobs = 12), then the tool calculates the funding gap, the months-to-goal at your savings rate, and interest earned if a HYSA APY is provided. Mirrors the site's emergency fund calculator engine exactly.
Input schema
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
monthlyExpenses | number (USD) | yes | 1 – 1,000,000 | Monthly essential expenses |
currentSavings | number (USD) | no | 0 – 10,000,000 | Liquid savings already set aside; default 0 |
monthlySavings | number (USD) | no | 0 – 100,000 | Monthly contribution toward the goal; default 0 |
employmentType | enum | no | employed · self-employed · freelance · multiple · between-jobs | Default employed; drives recommended months |
incomeEarners | enum | no | single · dual | Default single |
dependents | integer | no | 0 – 20 | Financial dependents; default 0 |
savingsAPY | number | no | 0 – 15 | HYSA APY as whole-number percent; default 0 |
Output payload
Example ($4,500/month expenses, $8,000 saved, $600/month savings, self-employed, single earner, 2 dependents, 4.3% APY):
{
"recommendedMonths": 9,
"targetFund": 40500.00,
"currentSavings": 8000.00,
"fundGap": 32500.00,
"percentComplete": 19.75,
"monthsToGoal": 48,
"interestEarned": 4174.89,
"futureValue": 40974.89
}monthsToGoal semantics
monthsToGoal is compounding-aware: the first month the balance — including savingsAPY interest — reaches the target, consistent with futureValue (and shorter than a naive gap ÷ monthly-savings when APY > 0). It is 0 ONLY when the goal is already funded, and null when the goal is not reachable at the current contribution — a 0-month timeline is never reported for an unfunded goal.
Bearer-tier tool: generate_report
Bearer tier only
This tool — the only one beyond the anonymous surface — is available ONLY to Bearer-tier sessions (an Authorization: Bearer dc_bearer_… header on the request). It is never present in the anonymous tools/list response, and an anonymous tools/call generate_report returns a structured authentication_required error rather than being silently listed or ignored. See Pricing for Bearer-tier access.
Renders a vector-text PDF report for a report-capable calculator tool and returns a short summary plus a short-lived presigned download link. Uses the same rendering pipeline as the Bearer-gated REST endpoint POST /v1/reports/{tool} (ADR-0055), metered against a daily report quota that is SEPARATE from the calculator-call quota. A Bearer key can carry a per-key BrandConfig that white-labels the rendered report. For a step-by-step walkthrough (curl on both transports, download, branding), see Server-rendered PDF reports in the Quickstart.
Where the server renderer is used
This tool and POST /v1/reports/{tool} are the only surfaces that render a report on our servers — your input is sent to the report renderer, and the resulting PDF is written to a presigned URL that expires. The calculator pages on the website do not use this path: since 2026-08-11 their report button builds the same document in the browser from the same engines and templates, so nothing about that calculation leaves the device. The two renderers share their engines and page templates; the browser one additionally embeds the site’s brand fonts and a chart.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
tool | string | yes | Report-capable tool alias: mortgage, loan, retirement-401k, education, or credit-card |
input | object | yes | The target tool's input object — same shape as that tool's calculator input (e.g., for mortgage: principal, annualRatePercent, termYears) |
These are the only two arguments (additionalProperties: false). In particular there is no per-request brand argument — branding is configured on the key, not the call. See Branding.
Output
On success, the tool returns an MCP content array with a short text summary followed by a resource_link content block pointing at the presigned PDF URL, plus a structuredContent object mirroring the REST report envelope:
{
"report": {
"url": "https://….s3.amazonaws.com/reports/mortgage/<hash>.pdf?X-Amz-…",
"expiresInSeconds": 900,
"cached": false,
"contentType": "application/pdf",
"cacheKey": "<hash>"
},
"tool": "mortgage",
"engineVersion": "1.0.0",
"calculatedAt": "2026-07-06T00:00:00.000Z",
"disclaimer": "Estimates only — see methodology.",
"methodology": { "url": "https://www.digitalcalculator.info/mortgage-calculator/methodology/", "version": "2026-05-09" }
}The presigned URL is short-lived (15 minutes by default) and unguessable. The rendered PDF carries the full numeric detail (amortization table, per-tool figures) — the tool result intentionally returns a short summary, not a re-derivation of the underlying calculator's output. Identical inputs are served from an artifact cache (cached: true): re-requesting after the link expires returns a fresh presigned URL without re-rendering.
Branding (per-key BrandConfig)
A Bearer key record can carry a BrandConfig that white-labels the rendered PDF's presentation. It is stored server-side on the key — there is no request parameter, and the same generate_report call renders branded once the key carries it. All five fields are optional; each is validated independently, so an invalid logoUrl drops only the logo, never the whole config.
| Field | Constraints | What it changes |
|---|---|---|
companyName | up to 60 characters | Replaces "Digital Calculator" as the report's branding name |
tagline | up to 80 characters | Replaces the tagline under the branding name |
logoUrl | https URL of an image (png/jpg/gif/webp/svg) on a public host — private/internal hosts are rejected | Adds your logo above the branding name, rendered up to 48px tall / 180px wide |
primaryColor | 6-digit hex (#rrggbb) | The report's slate chrome: header rule, branding name, results-table header bar, disclaimer bar |
accentColor | 6-digit hex (#rrggbb) | The copper accent on the input summary |
Setting or changing it: include the fields when you request a Bearer key, or email admin@markcolabs.com from the address that requested the key. The config is applied to the key record and your next render carries it. The artifact cache is partitioned per brand, so a branding change produces fresh renders rather than a stale-brand cache hit.
Presentation-only invariant
BrandConfig restyles chrome and identity (name, tagline, logo, colors) and nothing else. It never changes a financial number, an input value, a metric, or a table cell, and the "Not financial advice" core disclaimer is non-removable on every branded render.
Errors
authentication_required— no (or an invalid) Bearer token; anonymous callers always get thisreport_quota_exceeded— the key's separate daily report quota (default 25/day) was exceededrate_limited— the per-key 5-minute short-term ceiling was exceeded (the report surface shares your key's window with the calculator tools)REPORT_UNAVAILABLE— thetoolalias isn't (yet) report-capable; the message names the currently supported aliasesINPUT_VALIDATION— missingtool/input, or the target tool rejectedinputINTERNAL— report generation failed; report it if you see one
Tool: money_flow_map
Free anonymous tool (since v0.11.x)
Originally Bearer-tier only (v0.9.x–v0.10.x), money_flow_map is available anonymously as of v0.11.x: it appears in the anonymous tools/list alongside the 8 calculator tools and is callable without an API key under the standard per-IP anonymous rate limits. Describe a monthly budget as structured incomes and categories, and the tool returns a computed summary plus mapUrl — a link that renders the budget as an interactive money-flow (Sankey) map on this site, with nothing stored server-side.
Turns a structured monthly budget — income sources and spending categories, optionally broken into line items — into a deterministic computed summary plus mapUrl, a shareable deep link that renders the budget as an interactive Sankey money-flow map on the Money Flow Visualizer instantly (no account, nothing stored server-side). Built for conversational budgeting: the calling model interviews the user, structures the answers, and hands back the map link. The tool is read-only and deterministic like the 8 calculator tools — it accepts no free text and does no interpretation. All amounts are MONTHLY USD. Methodology: How This Tool Works.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
incomes | array (1–20) | yes | Monthly income sources: { label, amount } each. Take-home amounts work best for a spendable-dollars map. Amounts are 0–10,000,000 USD/month; labels are 1–80 characters |
categories | array (0–30) | yes | Spending categories: { label, amount } for a flat monthly total, OR { label, items } where items (1–30) is an array of { label, amount } line items that roll up into the category total (e.g., Rent + Utilities under Housing). Providing both amount and items is rejected. May be empty — savings is never a category; it is the computed residual |
label | string | no | Scenario name shown on the map (e.g., "Saving for a house"), up to 80 characters |
Validation is strict: unknown fields anywhere in the input are rejected with INPUT_VALIDATION, as are negative, non-finite, or out-of-range amounts.
Output payload
Returns the standard ToolResult envelope (disclaimer, methodology, rule_year, sources, calculatedAt, engineVersion) whose result is:
{
"label": "Saving for a house",
"totalMonthlyIncome": 6600,
"totalMonthlySpending": 3130,
"monthlySavings": 3470,
"deficit": false,
"savingsRatePercent": 52.5757575757…,
"categories": [
{ "label": "Housing", "monthlyTotal": 2030, "percentOfIncome": 30.7575… },
{ "label": "Food", "monthlyTotal": 700, "percentOfIncome": 10.6060… }
],
"largestCategories": [ { "label": "Housing", "monthlyTotal": 2030, "percentOfIncome": 30.7575… } ],
"mapUrl": "https://www.digitalcalculator.info/money-flow-visualizer/#s=<base64url>"
}monthlySavingsis the residual (income − spending) and may be negative;deficitistruewhen spending exceeds income — the map shows a deficit banner instead of an invented savings flowsavingsRatePercent= savings ÷ income × 100 (0when income is 0);percentOfIncomeuses the same conventionlargestCategorieslists up to the 3 largest categories by monthly total, descendingmapUrlencodes the budget in the visualizer’s versioned v1 URL-state schema (#s=<base64url(JSON)>) — opening it renders the interactive map client-side; nothing is uploaded or stored- Monetary values are full floating-point precision (not pre-rounded) — display-round to cents
Errors
INPUT_VALIDATION— empty/missingincomes, out-of-range or non-finite amounts, over-cap list lengths, unknown fields, or a category with bothamountanditems(thefieldproperty names the offending path)INTERNAL— computation failed; report it if you see one
Legacy REST aliases (compatibility guarantee)
The REST twin is not directory-frozen and keeps every pre-1.0 alias working indefinitely: POST https://api.digitalcalculator.info/v1/tools/{alias}/calculate dispatches to the same engines with the request/response shapes those aliases have always had — including their original field spellings (filingStatus, birthYear, etc.), which are preserved verbatim via a tested translation layer. These names are not part of the MCP tools/list surface. New integrations should prefer the canonical 1.0 equivalents.
| Legacy REST alias | Legacy tool name (wire vocabulary preserved) | Canonical 1.0 equivalent |
|---|---|---|
retirement-401k | retirement_401k_projection | plan_retirement_income (include: ["accumulation"]) |
social-security | social_security_estimated_benefit | plan_retirement_income (include: ["social_security"]) |
rmd | rmd_distribution_amount | plan_retirement_income (include: ["rmd"]) |
roth-conversion | roth_conversion_tax_impact | evaluate_roth_conversion |
ira | ira_contribution_limit | check_contribution_eligibility (kind: "trad_ira" / "roth_ira") |
hsa | hsa_contribution_limit | check_contribution_eligibility (kind: "hsa") |
compound-interest | compound_interest_future_value | project_growth |
savings | savings_future_balance | project_growth (monthly compounding default) |
inflation | inflation_adjusted_value | project_growth (inflation_rate_percent) |
paycheck | paycheck_net_pay (legacy wire: federalFilingStatus + its enum) | paycheck_net_pay (canonical filing_status) |
mortgage, loan, emergency-fund | unchanged keepers | same tools, same shapes |
The four scenario tools additionally have canonical kebab-case REST aliases: plan-retirement-income, evaluate-roth-conversion, check-contribution-eligibility, project-growth. Published line: MCP surface redesigned for agents; REST unchanged and extended.
Prompts
The server advertises the prompts capability with three parameterized prompts via prompts/list / prompts/get. Prompt argument values are strings per the MCP spec; each prompt instructs the model to convert them to the types the tool schemas require, and each generated message carries the YMYL framing (educational estimates, not advice).
| Prompt | What it drives | Required arguments |
|---|---|---|
retirement-readiness-review | One linked plan_retirement_income call with all three modules, then a plain-language review | birth_year, annual_salary, balance (optional: contribution_percent, retirement_age, claim_age) |
conversion-window-analysis | evaluate_roth_conversion plus a dc://irs/limits read for bracket-headroom sizing | birth_year, filing_status, current_taxable_income (optional: conversion_amount) |
withholding-checkup | paycheck_net_pay as an annual W-4 sanity check against the user's actual paycheck | gross_annual_salary, state, filing_status (optional: pay_frequency) |
Resources
The MCP server exposes five dc:// resources, all anonymous-readable (plus ui:// view templates for MCP Apps-capable hosts). The three statutory reference-data resources are the same tables the engines compute from — and the same URIs every tool cites in its sources[]:
| URI | Content |
|---|---|
dc://irs/limits/2026 | Contribution limits, catch-up tiers, phase-out bands, and federal brackets for the current rule year, with published-source citations (IRS Notice 2025-67, Rev. Proc. 2025-19/2025-32) |
dc://irs/uniform-lifetime | IRS Uniform Lifetime Table distribution periods + SECURE 2.0 RMD start-age cohorts |
dc://ssa/bend-points/2026 | SSA PIA bend points, the Social Security wage base, and COLA scope |
dc://disclaimers/ymyl | The canonical YMYL disclaimer string returned in every tool response's disclaimer field |
dc://methodologies/manifest | JSON manifest mapping each anonymous tool to the methodology URL of its source page — for agent-side citation of where each formula is documented |
Agents that need the reference table rather than a computation (e.g., "what's the 2026 401(k) limit?") should read the resource directly instead of calling a tool. Every resources/read body is static per deploy and advertises 24-hour public cacheability via _meta.
Get a key (self-serve)
A free key is minted by an unauthenticated POST — no signup, no email, no card. It works on your next request.
curl -X POST https://api.digitalcalculator.info/v1/keys/public
Returns HTTP 201 with the key and its allowance:
{
"key": "dc_pub_…",
"tier": "free",
"dailyLimit": 1000,
"windowLimit": 60,
"claimed": false
}
Then send it as Authorization: Bearer dc_pub_… on any tool call, on either transport.
The key is returned once
We store only its SHA-256 hash, so the key cannot be recovered, re-sent, or emailed — there is no account and no key list yet. Save it when you receive it; if you lose it, mint another. Minting is capped at 5 keys per IP per UTC day.
dc_pub_ keys are the publishable class: they carry the free allowance and are safe to place in client-side code. dc_bearer_ keys are the secret class issued for paid plans — they unlock the Bearer-tier surface (including generate_report) and must never appear in client-side source. Paid keys are created automatically when a subscription starts and shown on the confirmation page for 24 hours.
Rate limits
Two layers apply to every call. The outer layer is a single global API Gateway throttle of about 100 requests/minute (burst 50), shared by all callers — per-API, not per-caller. No tier reserves throughput against it, so a per-tier allowance is a ceiling on consumption, not a guarantee of rate. The inner layer is a per-caller allowance enforced in the application:
- Anonymous tier: 500 calls per UTC day per IP, plus a short-term ceiling of 30 calls per 5-minute window per IP. Exceeding either returns HTTP 429 +
code: "RATE_LIMIT"(retriable: true) withRetry-AfterandX-RateLimit-Limit / -Remaining / -Resetheaders. Terms:LICENSE-API.md - Free key tier (
dc_pub_, minted self-serve): 1,000 calls per UTC day per key, plus 60 calls per 5-minute window per key. Counted against your key alone rather than shared with everyone behind your IP - Bearer tier: a per-key daily cap plus a per-key fixed-window ceiling (boundary-aligned 5-minute buckets), by plan — authenticate with an
Authorization: Bearer <key>header. The daily budget is counted against your key alone and is not consumed by anonymous traffic. See plans and allowances - One meter per key, both transports: a key's allowance is decremented identically whether the call arrives on
/mcpor/v1/tools/{tool}/calculate. There is no second budget to discover - Successful anonymous responses are unchanged: the anonymous gate adds no headers on a PASS (anonymous-parity invariant). The only externally visible difference is the 429 itself
- Bearer is server-to-server today: the API's CORS policy does not yet allow the
Authorizationheader from browsers, so call the endpoint from a backend, CLI, or MCP client — not browser-sidefetch - Need sustained higher volume, or throughput genuinely reserved for you? Email us about Enterprise
Versioning
| Bump | Trigger | Example |
|---|---|---|
| Major | Tool rename, removed field, breaking envelope change | Contract 1.0.0: the pre-submission surface consolidation (granular tools absorbed into the four scenario tools) — the 0.x era permitted breaking changes; 1.0.0 is the version the deprecation contract now protects |
| Minor | Additive tool, additive output field, tool tier change | v0.8.x added generate_report (Bearer-tier only); v0.11.x promoted money_flow_map to the anonymous tier |
| Patch | Bug fixes, metadata corrections | Engine defect fixes |
engineVersion | Math changes only | Per-tool SemVer, independent of the package version |
methodology.version | Calculator's methodology document materially updated | ISO date string |
References
- Quickstart — connect + first call
- Changelog — release history including the 1.0.0 entry
- Versioning & Deprecation Contract — the stability rules this surface is governed by
- FAQ — accuracy, support, change policy
- npm package (stdio shim)
- CHANGELOG / version history (npm)
- LICENSE (MIT — package source, ships in the npm package)
- LICENSE-API.md — hosted endpoint terms (acceptable use, rate limits, no warranty)
- ADR-0039 — MCP Tool Contract (internal design doc)
- ADR-0064 — 1.0 Surface Consolidation (internal design doc)
Questions or integration help: email admin@markcolabs.com.