@savvly/mcp-server 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +4 -0
- package/README.md +50 -50
- package/dist/cli.js +283 -93
- package/dist/cli.js.map +1 -1
- package/package.json +47 -40
package/dist/cli.js
CHANGED
|
@@ -3105,6 +3105,9 @@ var require_utils = __commonJS({
|
|
|
3105
3105
|
"use strict";
|
|
3106
3106
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3107
3107
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3108
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3109
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3110
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3108
3111
|
function stringArrayToHexStripped(input) {
|
|
3109
3112
|
let acc = "";
|
|
3110
3113
|
let code = 0;
|
|
@@ -3297,27 +3300,77 @@ var require_utils = __commonJS({
|
|
|
3297
3300
|
}
|
|
3298
3301
|
return output.join("");
|
|
3299
3302
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3303
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3304
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3305
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3306
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3307
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3308
|
+
re.lastIndex = 0;
|
|
3309
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3310
|
+
}
|
|
3311
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3312
|
+
if (input.indexOf("%") === -1) {
|
|
3313
|
+
return input;
|
|
3310
3314
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3315
|
+
let output = "";
|
|
3316
|
+
for (let i = 0; i < input.length; i++) {
|
|
3317
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3318
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3319
|
+
if (isHexPair(hex)) {
|
|
3320
|
+
const normalizedHex = hex.toUpperCase();
|
|
3321
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3322
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3323
|
+
output += decoded;
|
|
3324
|
+
} else {
|
|
3325
|
+
output += "%" + normalizedHex;
|
|
3326
|
+
}
|
|
3327
|
+
i += 2;
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
output += input[i];
|
|
3313
3332
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3333
|
+
return output;
|
|
3334
|
+
}
|
|
3335
|
+
function normalizePathEncoding(input) {
|
|
3336
|
+
let output = "";
|
|
3337
|
+
for (let i = 0; i < input.length; i++) {
|
|
3338
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3339
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3340
|
+
if (isHexPair(hex)) {
|
|
3341
|
+
const normalizedHex = hex.toUpperCase();
|
|
3342
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3343
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3344
|
+
output += decoded;
|
|
3345
|
+
} else {
|
|
3346
|
+
output += "%" + normalizedHex;
|
|
3347
|
+
}
|
|
3348
|
+
i += 2;
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
if (isPathCharacter(input[i])) {
|
|
3353
|
+
output += input[i];
|
|
3354
|
+
} else {
|
|
3355
|
+
output += escape(input[i]);
|
|
3356
|
+
}
|
|
3316
3357
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3358
|
+
return output;
|
|
3359
|
+
}
|
|
3360
|
+
function escapePreservingEscapes(input) {
|
|
3361
|
+
let output = "";
|
|
3362
|
+
for (let i = 0; i < input.length; i++) {
|
|
3363
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3364
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3365
|
+
if (isHexPair(hex)) {
|
|
3366
|
+
output += "%" + hex.toUpperCase();
|
|
3367
|
+
i += 2;
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
output += escape(input[i]);
|
|
3319
3372
|
}
|
|
3320
|
-
return
|
|
3373
|
+
return output;
|
|
3321
3374
|
}
|
|
3322
3375
|
function recomposeAuthority(component) {
|
|
3323
3376
|
const uriTokens = [];
|
|
@@ -3332,7 +3385,7 @@ var require_utils = __commonJS({
|
|
|
3332
3385
|
if (ipV6res.isIPV6 === true) {
|
|
3333
3386
|
host = `[${ipV6res.escapedHost}]`;
|
|
3334
3387
|
} else {
|
|
3335
|
-
host =
|
|
3388
|
+
host = reescapeHostDelimiters(host, false);
|
|
3336
3389
|
}
|
|
3337
3390
|
}
|
|
3338
3391
|
uriTokens.push(host);
|
|
@@ -3346,7 +3399,10 @@ var require_utils = __commonJS({
|
|
|
3346
3399
|
module.exports = {
|
|
3347
3400
|
nonSimpleDomain,
|
|
3348
3401
|
recomposeAuthority,
|
|
3349
|
-
|
|
3402
|
+
reescapeHostDelimiters,
|
|
3403
|
+
normalizePercentEncoding,
|
|
3404
|
+
normalizePathEncoding,
|
|
3405
|
+
escapePreservingEscapes,
|
|
3350
3406
|
removeDotSegments,
|
|
3351
3407
|
isIPv4,
|
|
3352
3408
|
isUUID,
|
|
@@ -3570,12 +3626,12 @@ var require_schemes = __commonJS({
|
|
|
3570
3626
|
var require_fast_uri = __commonJS({
|
|
3571
3627
|
"../../node_modules/fast-uri/index.js"(exports, module) {
|
|
3572
3628
|
"use strict";
|
|
3573
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3629
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3574
3630
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3575
3631
|
function normalize(uri, options) {
|
|
3576
3632
|
if (typeof uri === "string") {
|
|
3577
3633
|
uri = /** @type {T} */
|
|
3578
|
-
|
|
3634
|
+
normalizeString(uri, options);
|
|
3579
3635
|
} else if (typeof uri === "object") {
|
|
3580
3636
|
uri = /** @type {T} */
|
|
3581
3637
|
parse3(serialize(uri, options), options);
|
|
@@ -3642,19 +3698,9 @@ var require_fast_uri = __commonJS({
|
|
|
3642
3698
|
return target;
|
|
3643
3699
|
}
|
|
3644
3700
|
function equal(uriA, uriB, options) {
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
} else if (typeof uriA === "object") {
|
|
3649
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3650
|
-
}
|
|
3651
|
-
if (typeof uriB === "string") {
|
|
3652
|
-
uriB = unescape(uriB);
|
|
3653
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
3654
|
-
} else if (typeof uriB === "object") {
|
|
3655
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3656
|
-
}
|
|
3657
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3701
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3702
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3703
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3658
3704
|
}
|
|
3659
3705
|
function serialize(cmpts, opts) {
|
|
3660
3706
|
const component = {
|
|
@@ -3679,12 +3725,12 @@ var require_fast_uri = __commonJS({
|
|
|
3679
3725
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3680
3726
|
if (component.path !== void 0) {
|
|
3681
3727
|
if (!options.skipEscape) {
|
|
3682
|
-
component.path =
|
|
3728
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3683
3729
|
if (component.scheme !== void 0) {
|
|
3684
3730
|
component.path = component.path.split("%3A").join(":");
|
|
3685
3731
|
}
|
|
3686
3732
|
} else {
|
|
3687
|
-
component.path =
|
|
3733
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3688
3734
|
}
|
|
3689
3735
|
}
|
|
3690
3736
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3719,7 +3765,16 @@ var require_fast_uri = __commonJS({
|
|
|
3719
3765
|
return uriTokens.join("");
|
|
3720
3766
|
}
|
|
3721
3767
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3722
|
-
function
|
|
3768
|
+
function getParseError(parsed, matches) {
|
|
3769
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3770
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3771
|
+
}
|
|
3772
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3773
|
+
return "URI port is malformed.";
|
|
3774
|
+
}
|
|
3775
|
+
return void 0;
|
|
3776
|
+
}
|
|
3777
|
+
function parseWithStatus(uri, opts) {
|
|
3723
3778
|
const options = Object.assign({}, opts);
|
|
3724
3779
|
const parsed = {
|
|
3725
3780
|
scheme: void 0,
|
|
@@ -3730,6 +3785,7 @@ var require_fast_uri = __commonJS({
|
|
|
3730
3785
|
query: void 0,
|
|
3731
3786
|
fragment: void 0
|
|
3732
3787
|
};
|
|
3788
|
+
let malformedAuthorityOrPort = false;
|
|
3733
3789
|
let isIP = false;
|
|
3734
3790
|
if (options.reference === "suffix") {
|
|
3735
3791
|
if (options.scheme) {
|
|
@@ -3750,6 +3806,11 @@ var require_fast_uri = __commonJS({
|
|
|
3750
3806
|
if (isNaN(parsed.port)) {
|
|
3751
3807
|
parsed.port = matches[5];
|
|
3752
3808
|
}
|
|
3809
|
+
const parseError = getParseError(parsed, matches);
|
|
3810
|
+
if (parseError !== void 0) {
|
|
3811
|
+
parsed.error = parsed.error || parseError;
|
|
3812
|
+
malformedAuthorityOrPort = true;
|
|
3813
|
+
}
|
|
3753
3814
|
if (parsed.host) {
|
|
3754
3815
|
const ipv4result = isIPv4(parsed.host);
|
|
3755
3816
|
if (ipv4result === false) {
|
|
@@ -3788,14 +3849,18 @@ var require_fast_uri = __commonJS({
|
|
|
3788
3849
|
parsed.scheme = unescape(parsed.scheme);
|
|
3789
3850
|
}
|
|
3790
3851
|
if (parsed.host !== void 0) {
|
|
3791
|
-
parsed.host = unescape(parsed.host);
|
|
3852
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3792
3853
|
}
|
|
3793
3854
|
}
|
|
3794
3855
|
if (parsed.path) {
|
|
3795
|
-
parsed.path =
|
|
3856
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3796
3857
|
}
|
|
3797
3858
|
if (parsed.fragment) {
|
|
3798
|
-
|
|
3859
|
+
try {
|
|
3860
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3861
|
+
} catch {
|
|
3862
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3863
|
+
}
|
|
3799
3864
|
}
|
|
3800
3865
|
}
|
|
3801
3866
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3804,7 +3869,29 @@ var require_fast_uri = __commonJS({
|
|
|
3804
3869
|
} else {
|
|
3805
3870
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3806
3871
|
}
|
|
3807
|
-
return parsed;
|
|
3872
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3873
|
+
}
|
|
3874
|
+
function parse3(uri, opts) {
|
|
3875
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3876
|
+
}
|
|
3877
|
+
function normalizeString(uri, opts) {
|
|
3878
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3879
|
+
}
|
|
3880
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3881
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3882
|
+
return {
|
|
3883
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3884
|
+
malformedAuthorityOrPort
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
function normalizeComparableURI(uri, opts) {
|
|
3888
|
+
if (typeof uri === "string") {
|
|
3889
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3890
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
3891
|
+
}
|
|
3892
|
+
if (typeof uri === "object") {
|
|
3893
|
+
return serialize(uri, opts);
|
|
3894
|
+
}
|
|
3808
3895
|
}
|
|
3809
3896
|
var fastUri = {
|
|
3810
3897
|
SCHEMES,
|
|
@@ -21616,6 +21703,9 @@ var PRODUCT_COMPARISON = {
|
|
|
21616
21703
|
};
|
|
21617
21704
|
var VALID_PRODUCT_TYPES = COMPARISONS.map((c) => c.product_type);
|
|
21618
21705
|
|
|
21706
|
+
// ../../src/mcp/version.ts
|
|
21707
|
+
var SERVER_VERSION = "1.0.2";
|
|
21708
|
+
|
|
21619
21709
|
// ../../src/data/faq.ts
|
|
21620
21710
|
var FAQ = [
|
|
21621
21711
|
{
|
|
@@ -22256,7 +22346,7 @@ var QA_LIBRARY = [
|
|
|
22256
22346
|
"question": "What CTAs work for benefit brokers and employers?",
|
|
22257
22347
|
"answer": "Book a Demo or Learn more at savvly.com. \u2014 Ready to offer a benefit that lasts a lifetime? Book a demo or explore our payout estimator online. \u2014 Three steps to launch: Book a discovery call, Design your plan, Go live in days. \u2014 Learn how Savvly can help your employees build a future grounded in confidence.",
|
|
22258
22348
|
"footnotes": [
|
|
22259
|
-
"Savvly's Longevity Benefit is structured as a pooled investment plan offered through the Savvly Fund, a registered closed-end fund under the Investment Company Act of 1940. It is not a guaranteed income product, not insurance, and not FDIC insured. Participation involves investment risk, including the possible loss of principal. Payouts are contingent on factors including market performance, mortality experience, and the timing of withdrawals. Savvly Advisor, LLC is a registered investment adviser. This material is for informational purposes only and does not constitute personalized investment advice. For complete information on risks, fees, structure, and eligibility, visit savvly.com/
|
|
22349
|
+
"Savvly's Longevity Benefit is structured as a pooled investment plan offered through the Savvly Fund, a registered closed-end fund under the Investment Company Act of 1940. It is not a guaranteed income product, not insurance, and not FDIC insured. Participation involves investment risk, including the possible loss of principal. Payouts are contingent on factors including market performance, mortality experience, and the timing of withdrawals. Savvly Advisor, LLC is a registered investment adviser. This material is for informational purposes only and does not constitute personalized investment advice. For complete information on risks, fees, structure, and eligibility, visit savvly.com/disclosures."
|
|
22260
22350
|
],
|
|
22261
22351
|
"id": "universal/calls-to-action-by-audience/what-ctas-work-for-benefit-brokers-and-employers"
|
|
22262
22352
|
}
|
|
@@ -22333,17 +22423,9 @@ var AdvisorClientError = class extends Error {
|
|
|
22333
22423
|
status;
|
|
22334
22424
|
responseBody;
|
|
22335
22425
|
};
|
|
22336
|
-
var
|
|
22337
|
-
constructor() {
|
|
22338
|
-
super(
|
|
22339
|
-
"advisor_functions URL is not configured. Set ADVISOR_FUNCTIONS_URL (e.g. https://advisor-functions.example.net/api) to enable projections."
|
|
22340
|
-
);
|
|
22341
|
-
this.name = "AdvisorNotConfigured";
|
|
22342
|
-
}
|
|
22343
|
-
};
|
|
22426
|
+
var DEFAULT_ADVISOR_FUNCTIONS_URL = "https://savvly-estimator.azurewebsites.net";
|
|
22344
22427
|
function getBaseUrl() {
|
|
22345
|
-
const url = process.env.ADVISOR_FUNCTIONS_URL;
|
|
22346
|
-
if (!url) throw new AdvisorNotConfigured();
|
|
22428
|
+
const url = process.env.ADVISOR_FUNCTIONS_URL ?? DEFAULT_ADVISOR_FUNCTIONS_URL;
|
|
22347
22429
|
const trimmed = url.replace(/\/+$/, "");
|
|
22348
22430
|
return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
|
|
22349
22431
|
}
|
|
@@ -22443,42 +22525,42 @@ var PayoutAgeRowSchema = external_exports.object({
|
|
|
22443
22525
|
"Milestone age (in years) at which this payout row is reported. The estimator returns rows for ages 80, 85, 90, and 95 by default."
|
|
22444
22526
|
),
|
|
22445
22527
|
savvly_payout_lower: external_exports.number().describe(
|
|
22446
|
-
"USD. Lower bound of the modeled payout
|
|
22528
|
+
"USD. Lower bound of the modeled payout with Savvly at this milestone age."
|
|
22447
22529
|
),
|
|
22448
22530
|
savvly_payout_upper: external_exports.number().describe(
|
|
22449
|
-
"USD. Upper bound (central illustrative estimate) of the payout
|
|
22531
|
+
"USD. Upper bound (central illustrative estimate) of the payout with Savvly at this milestone age. Use this when surfacing a single number."
|
|
22450
22532
|
),
|
|
22451
|
-
|
|
22452
|
-
"USD. Lower bound of the counterfactual
|
|
22533
|
+
payout_market_alone_lower: external_exports.number().describe(
|
|
22534
|
+
"USD. Lower bound of the counterfactual market alone payout at this age. May be 0 once the modeled market alone portfolio has been fully drawn down by withdrawals."
|
|
22453
22535
|
),
|
|
22454
|
-
|
|
22455
|
-
"USD. Upper bound of the counterfactual
|
|
22536
|
+
payout_market_alone_upper: external_exports.number().describe(
|
|
22537
|
+
"USD. Upper bound of the counterfactual market alone payout at this age. May be 0 once the market alone portfolio has been depleted."
|
|
22456
22538
|
),
|
|
22457
22539
|
savvly_upside_lower: external_exports.number().describe(
|
|
22458
|
-
"USD. Lower bound for the incremental payout at this age attributable to Savvly's Longevity Benefit (savvly_payout \u2212
|
|
22540
|
+
"USD. Lower bound for the incremental payout at this age attributable to Savvly's Longevity Benefit (savvly_payout \u2212 payout_market_alone)."
|
|
22459
22541
|
),
|
|
22460
22542
|
savvly_upside_upper: external_exports.number().describe(
|
|
22461
|
-
"USD. Upper bound for the incremental Savvly
|
|
22543
|
+
"USD. Upper bound for the incremental Savvly upside at this age. Tends to be large at 90/95 where the market alone portfolio is depleted."
|
|
22462
22544
|
)
|
|
22463
22545
|
});
|
|
22464
22546
|
var PayoutEnvelopeSchema = external_exports.object({
|
|
22465
22547
|
total_savvly_upside_lower: external_exports.number().describe(
|
|
22466
|
-
"USD. Lower bound of the cumulative incremental upside Savvly provides vs. investing in
|
|
22548
|
+
"USD. Lower bound of the cumulative incremental upside Savvly provides vs. investing in the market alone, summed across all milestone payout ages."
|
|
22467
22549
|
),
|
|
22468
22550
|
total_savvly_upside_upper: external_exports.number().describe(
|
|
22469
|
-
"USD. Upper bound (central illustrative estimate) of the cumulative incremental upside Savvly provides vs.
|
|
22551
|
+
"USD. Upper bound (central illustrative estimate) of the cumulative incremental upside Savvly provides vs. market alone. Equals `total_savvly_upper \u2212 total_market_alone_upper`."
|
|
22470
22552
|
),
|
|
22471
22553
|
total_savvly_lower: external_exports.number().describe(
|
|
22472
|
-
"USD. Lower bound of the total cumulative payout
|
|
22554
|
+
"USD. Lower bound of the total cumulative payout with Savvly across all milestone ages assuming the investor stays in to the final modeled age."
|
|
22473
22555
|
),
|
|
22474
22556
|
total_savvly_upper: external_exports.number().describe(
|
|
22475
|
-
"USD. Upper bound of the total cumulative payout
|
|
22557
|
+
"USD. Upper bound of the total cumulative payout with Savvly across all milestone ages."
|
|
22476
22558
|
),
|
|
22477
|
-
|
|
22478
|
-
"USD. Lower bound of the cumulative
|
|
22559
|
+
total_market_alone_lower: external_exports.number().describe(
|
|
22560
|
+
"USD. Lower bound of the cumulative counterfactual market alone payout."
|
|
22479
22561
|
),
|
|
22480
|
-
|
|
22481
|
-
"USD. Upper bound of the cumulative
|
|
22562
|
+
total_market_alone_upper: external_exports.number().describe(
|
|
22563
|
+
"USD. Upper bound of the cumulative counterfactual market alone payout."
|
|
22482
22564
|
),
|
|
22483
22565
|
early_withdrawal_value: external_exports.number().describe(
|
|
22484
22566
|
"USD. Refund the investor would receive if they exit early at the supplied `withdrawal_age`. Point estimate (no range). Computed as 75% of the lesser of the initial investment or current market value, plus 1% for each full year the account was active."
|
|
@@ -22496,7 +22578,7 @@ var PayoutEnvelopeSchema = external_exports.object({
|
|
|
22496
22578
|
"Percent. Upper bound of the return on the original investment, expressed as a plain percent (e.g. 1710 = 1,710%)."
|
|
22497
22579
|
),
|
|
22498
22580
|
payout_age_dependent_values: external_exports.array(PayoutAgeRowSchema).describe(
|
|
22499
|
-
"Per-milestone-age breakdown of the projection. Typically 4 rows for ages 80, 85, 90, 95. Each row contains the
|
|
22581
|
+
"Per-milestone-age breakdown of the projection. Typically 4 rows for ages 80, 85, 90, 95. Each row contains the with Savvly payout, the market alone counterfactual, and the incremental Savvly upside for that age, each with `_lower`/`_upper` bounds."
|
|
22500
22582
|
)
|
|
22501
22583
|
});
|
|
22502
22584
|
var RetirementAgeRowSchema = external_exports.object({
|
|
@@ -22529,6 +22611,17 @@ var RetirementEnvelopeSchema = external_exports.object({
|
|
|
22529
22611
|
"Per-age timeline across the planning horizon (current_age \u2192 life_expectancy). One row per year."
|
|
22530
22612
|
)
|
|
22531
22613
|
});
|
|
22614
|
+
var TopLevelDisclosureSchema = external_exports.object({
|
|
22615
|
+
required: external_exports.literal(true).describe(
|
|
22616
|
+
"Always true. Signals to the calling AI client that the `text` and `url` fields MUST be displayed to the end user whenever any number from this response is shown. Required by SEC Marketing Rule and FINRA Rule 2210."
|
|
22617
|
+
),
|
|
22618
|
+
text: external_exports.string().describe(
|
|
22619
|
+
"Short verbatim SEC disclosure text. Display this string to the user alongside any projection figures from this response. Do not paraphrase, summarize, or omit."
|
|
22620
|
+
),
|
|
22621
|
+
url: external_exports.string().describe(
|
|
22622
|
+
"Canonical full-disclosures URL. Render as a clickable link to the user alongside the `text`. Required by SEC Marketing Rule and FINRA Rule 2210."
|
|
22623
|
+
)
|
|
22624
|
+
});
|
|
22532
22625
|
var DisclaimerMetadataSchema = external_exports.object({
|
|
22533
22626
|
summary: external_exports.string().describe(
|
|
22534
22627
|
"Short illustrative-purposes disclaimer matching the text directly below the chart on estimator.savvly.com. Surface this whenever you summarize the projection to a user."
|
|
@@ -22554,14 +22647,14 @@ var ProjectionSummarySchema = external_exports.object({
|
|
|
22554
22647
|
"USD. Total amount deposited into Savvly. For single deposits this equals `funding_amount`. For monthly installments it equals `monthly_installment * installment_years * 12`."
|
|
22555
22648
|
),
|
|
22556
22649
|
total_savvly_upper_usd: external_exports.number().nullable().describe("Convenience copy of `result.total_savvly_upper`."),
|
|
22557
|
-
|
|
22650
|
+
total_market_alone_upper_usd: external_exports.number().nullable().describe("Convenience copy of `result.total_market_alone_upper`."),
|
|
22558
22651
|
savvly_above_market_upper_usd: external_exports.number().nullable().describe(
|
|
22559
|
-
"USD. `total_savvly_upper \u2212
|
|
22652
|
+
"USD. `total_savvly_upper \u2212 total_market_alone_upper` (the central incremental upside). May differ slightly from `result.total_savvly_upside_upper` due to rounding."
|
|
22560
22653
|
),
|
|
22561
22654
|
early_exit_refund_usd: external_exports.number().nullable().describe("Convenience copy of `result.early_withdrawal_value`."),
|
|
22562
22655
|
percentage_gain_upper_percent: external_exports.number().nullable().describe("Convenience copy of `result.percentage_gain_upper`."),
|
|
22563
22656
|
narrative: external_exports.string().describe(
|
|
22564
|
-
"Human-readable English sentence summarizing the projection. Always ends with
|
|
22657
|
+
"Human-readable English sentence summarizing the projection. Always ends with 'Payouts are not guarantees. See full disclosures at <url>.' \u2014 display the URL verbatim alongside any figures."
|
|
22565
22658
|
)
|
|
22566
22659
|
});
|
|
22567
22660
|
var ProjectionResponseSchema = external_exports.object({
|
|
@@ -22572,6 +22665,9 @@ var ProjectionResponseSchema = external_exports.object({
|
|
|
22572
22665
|
summary: ProjectionSummarySchema.describe(
|
|
22573
22666
|
"Convenience summary including a human-readable narrative."
|
|
22574
22667
|
),
|
|
22668
|
+
disclosure: TopLevelDisclosureSchema.describe(
|
|
22669
|
+
"DISCLOSURE REQUIRED: display `disclosure.text` and link `disclosure.url` to the user whenever you present any number from this response. Required by SEC Marketing Rule and FINRA Rule 2210. The richer block under `metadata.disclaimer` is supplementary detail; this top-level field is the must-display."
|
|
22670
|
+
),
|
|
22575
22671
|
metadata: external_exports.object({
|
|
22576
22672
|
disclaimer: DisclaimerMetadataSchema,
|
|
22577
22673
|
field_descriptions: external_exports.record(external_exports.string()).describe(
|
|
@@ -22579,21 +22675,58 @@ var ProjectionResponseSchema = external_exports.object({
|
|
|
22579
22675
|
)
|
|
22580
22676
|
})
|
|
22581
22677
|
});
|
|
22678
|
+
var DisplayHintsSchema = external_exports.object({
|
|
22679
|
+
chart: external_exports.object({
|
|
22680
|
+
type: external_exports.literal("area"),
|
|
22681
|
+
reference: external_exports.string(),
|
|
22682
|
+
x_axis: external_exports.object({ field: external_exports.string(), label: external_exports.string() }),
|
|
22683
|
+
y_axis: external_exports.object({ label: external_exports.string(), format: external_exports.string() }),
|
|
22684
|
+
series: external_exports.array(
|
|
22685
|
+
external_exports.object({
|
|
22686
|
+
field: external_exports.string(),
|
|
22687
|
+
label: external_exports.string(),
|
|
22688
|
+
color: external_exports.string(),
|
|
22689
|
+
fill: external_exports.string()
|
|
22690
|
+
})
|
|
22691
|
+
),
|
|
22692
|
+
interpolation: external_exports.literal("linear"),
|
|
22693
|
+
show_markers: external_exports.boolean(),
|
|
22694
|
+
tooltip: external_exports.record(external_exports.string())
|
|
22695
|
+
}),
|
|
22696
|
+
layout: external_exports.object({
|
|
22697
|
+
above_chart: external_exports.string(),
|
|
22698
|
+
below_chart: external_exports.string()
|
|
22699
|
+
}),
|
|
22700
|
+
rendering_notes: external_exports.array(external_exports.string())
|
|
22701
|
+
}).describe(
|
|
22702
|
+
"Rendering guidance for the retirement projection chart. Tells AI clients to render `age_dependent_values` as a stacked area chart matching estimator.savvly.com \u2014 linear interpolation (no spline), no markers, so Savvly milestone payouts at ages 80/85/90/95 appear as sharp spikes with visible year-by-year depletion between them. KPI cards above and a narrative paragraph below the chart are encouraged."
|
|
22703
|
+
);
|
|
22582
22704
|
var RetirementResponseSchema = external_exports.object({
|
|
22583
22705
|
inputs: external_exports.record(external_exports.unknown()),
|
|
22584
22706
|
result: RetirementEnvelopeSchema,
|
|
22707
|
+
summary: external_exports.object({
|
|
22708
|
+
narrative: external_exports.string().describe(
|
|
22709
|
+
"Human-readable English sentence. Always ends with the canonical full-disclosures URL so AI clients that paraphrase past `metadata.disclaimer` still echo the disclosure link."
|
|
22710
|
+
)
|
|
22711
|
+
}).describe(
|
|
22712
|
+
"Convenience summary. The narrative carries the canonical full-disclosures URL inline; display it verbatim alongside any figures from this response."
|
|
22713
|
+
),
|
|
22714
|
+
disclosure: TopLevelDisclosureSchema.describe(
|
|
22715
|
+
"DISCLOSURE REQUIRED: display `disclosure.text` and link `disclosure.url` to the user whenever you present any number from this response. Required by SEC Marketing Rule and FINRA Rule 2210."
|
|
22716
|
+
),
|
|
22585
22717
|
metadata: external_exports.object({
|
|
22586
22718
|
disclaimer: DisclaimerMetadataSchema,
|
|
22587
|
-
field_descriptions: external_exports.record(external_exports.string())
|
|
22719
|
+
field_descriptions: external_exports.record(external_exports.string()),
|
|
22720
|
+
display_hints: DisplayHintsSchema
|
|
22588
22721
|
})
|
|
22589
22722
|
});
|
|
22590
22723
|
var PAYOUT_FIELD_DESCRIPTIONS = {
|
|
22591
|
-
total_savvly_upside_lower: "USD. Lower bound of cumulative Savvly
|
|
22592
|
-
total_savvly_upside_upper: "USD. Upper bound (central illustrative) of cumulative Savvly
|
|
22593
|
-
total_savvly_lower: "USD. Lower bound of total cumulative payout
|
|
22594
|
-
total_savvly_upper: "USD. Upper bound of total cumulative payout
|
|
22595
|
-
|
|
22596
|
-
|
|
22724
|
+
total_savvly_upside_lower: "USD. Lower bound of cumulative Savvly upside vs market alone.",
|
|
22725
|
+
total_savvly_upside_upper: "USD. Upper bound (central illustrative) of cumulative Savvly upside vs market alone.",
|
|
22726
|
+
total_savvly_lower: "USD. Lower bound of total cumulative payout with Savvly through final age.",
|
|
22727
|
+
total_savvly_upper: "USD. Upper bound of total cumulative payout with Savvly through final age.",
|
|
22728
|
+
total_market_alone_lower: "USD. Lower bound of cumulative market alone counterfactual.",
|
|
22729
|
+
total_market_alone_upper: "USD. Upper bound of cumulative market alone counterfactual.",
|
|
22597
22730
|
early_withdrawal_value: "USD. Refund if investor exits at `withdrawal_age`. Point estimate (no range).",
|
|
22598
22731
|
total_payout_at_withdrawal_age_lower: "USD. Lower bound of total payout if exiting at `withdrawal_age`.",
|
|
22599
22732
|
total_payout_at_withdrawal_age_upper: "USD. Upper bound of total payout if exiting at `withdrawal_age`.",
|
|
@@ -22601,12 +22734,12 @@ var PAYOUT_FIELD_DESCRIPTIONS = {
|
|
|
22601
22734
|
percentage_gain_upper: "Percent. Upper bound of return on original investment.",
|
|
22602
22735
|
payout_age_dependent_values: "Array. Per-milestone-age breakdown (ages 80/85/90/95). See payout_age_dependent_values[] field descriptions.",
|
|
22603
22736
|
"payout_age_dependent_values[].payout_age": "Years. Milestone age this row reports.",
|
|
22604
|
-
"payout_age_dependent_values[].savvly_payout_lower": "USD. Lower bound payout
|
|
22605
|
-
"payout_age_dependent_values[].savvly_payout_upper": "USD. Upper bound payout
|
|
22606
|
-
"payout_age_dependent_values[].
|
|
22607
|
-
"payout_age_dependent_values[].
|
|
22608
|
-
"payout_age_dependent_values[].savvly_upside_lower": "USD. Lower bound for Savvly
|
|
22609
|
-
"payout_age_dependent_values[].savvly_upside_upper": "USD. Upper bound for Savvly
|
|
22737
|
+
"payout_age_dependent_values[].savvly_payout_lower": "USD. Lower bound payout with Savvly at this age.",
|
|
22738
|
+
"payout_age_dependent_values[].savvly_payout_upper": "USD. Upper bound payout with Savvly at this age.",
|
|
22739
|
+
"payout_age_dependent_values[].payout_market_alone_lower": "USD. Lower bound market alone counterfactual. May be 0 once market alone portfolio is depleted.",
|
|
22740
|
+
"payout_age_dependent_values[].payout_market_alone_upper": "USD. Upper bound market alone counterfactual at this age.",
|
|
22741
|
+
"payout_age_dependent_values[].savvly_upside_lower": "USD. Lower bound for the Savvly incremental upside at this age.",
|
|
22742
|
+
"payout_age_dependent_values[].savvly_upside_upper": "USD. Upper bound for the Savvly incremental upside at this age."
|
|
22610
22743
|
};
|
|
22611
22744
|
var RETIREMENT_FIELD_DESCRIPTIONS = {
|
|
22612
22745
|
gap_score: "Server-computed score summarizing the savings-vs-paycheck gap.",
|
|
@@ -22719,19 +22852,28 @@ function disclaimerBlock() {
|
|
|
22719
22852
|
full_disclosures_url: PRODUCT.disclaimers.full_disclosures_url
|
|
22720
22853
|
};
|
|
22721
22854
|
}
|
|
22855
|
+
function disclosureBlock() {
|
|
22856
|
+
return {
|
|
22857
|
+
required: true,
|
|
22858
|
+
text: PRODUCT.disclaimers.projections,
|
|
22859
|
+
url: PRODUCT.disclaimers.full_disclosures_url
|
|
22860
|
+
};
|
|
22861
|
+
}
|
|
22862
|
+
var DISCLOSURE_REQUIRED_TERSE = "DISCLOSURE REQUIRED: display `disclosure.text` verbatim and link `disclosure.url` to the user alongside any figures from this response. Required by SEC Marketing Rule and FINRA Rule 2210 \u2014 do not paraphrase or omit.";
|
|
22863
|
+
var SAVVLY_VS_MARKET_ALONE_PAYOUT_METHODOLOGY = "Payout methodology \u2014 Savvly vs market alone: the payout values are calculated by comparing two investors of the same age committing the same principal. Investor 1 invests in the market with Savvly's Longevity Benefit Fund; Investor 2 invests in the market alone (no longevity overlay). To make the comparison apples-to-apples, at each milestone age (80, 85, 90, 95) Investor 2 withdraws from their market alone portfolio the same dollar amount Investor 1 receives as a payout from Savvly. The `payout_market_alone_*` and `total_market_alone_*` figures are therefore what Investor 2 can actually withdraw to match Savvly's payouts before running out \u2014 they fall to 0 once the market alone portfolio is depleted. The `savvly_upside_*` (and `total_savvly_upside_*`) fields quantify how much more total money Investor 1 receives in payouts from Savvly than Investor 2 is able to withdraw over time to match those payouts.";
|
|
22722
22864
|
function summarizePayouts(envelope, depositType, deposited) {
|
|
22723
22865
|
const totalSavvly = envelope.total_savvly_upper ?? null;
|
|
22724
|
-
const totalMarket = envelope.
|
|
22866
|
+
const totalMarket = envelope.total_market_alone_upper ?? null;
|
|
22725
22867
|
const upside = totalSavvly !== null && totalMarket !== null ? totalSavvly - totalMarket : null;
|
|
22726
22868
|
const refund = envelope.early_withdrawal_value ?? null;
|
|
22727
22869
|
const pctGain = envelope.percentage_gain_upper ?? null;
|
|
22728
22870
|
const depositText = depositType === "single" ? `a single upfront deposit of ${formatUsd(deposited)}` : `monthly deposits totaling ${formatUsd(deposited)}`;
|
|
22729
|
-
const narrative = `With ${depositText}, the Savvly Longevity Benefit could pay out roughly ${formatUsd(totalSavvly)} cumulatively through age 95 \u2014 ${formatUsd(upside)} more than investing in the
|
|
22871
|
+
const narrative = `With ${depositText}, the Savvly Longevity Benefit could pay out roughly ${formatUsd(totalSavvly)} cumulatively through age 95 \u2014 ${formatUsd(upside)} more than investing in the market alone (${formatUsd(totalMarket)}). ` + (refund !== null ? `If the investor exits early at the chosen withdrawal age, they would get back about ${formatUsd(refund)}. ` : "") + (pctGain !== null ? `That's an illustrative ${pctGain}% return on the original investment. ` : "") + `All Savvly figures are illustrative and presented net of Savvly's management fees (55 bps on purchased shares, 110 bps on allocated shares; typically blended in the 55-70 bps range). Payouts are not guarantees. See full disclosures at ${PRODUCT.disclaimers.full_disclosures_url}.`;
|
|
22730
22872
|
return {
|
|
22731
22873
|
deposit_type: depositType,
|
|
22732
22874
|
deposited_amount_usd: deposited,
|
|
22733
22875
|
total_savvly_upper_usd: totalSavvly,
|
|
22734
|
-
|
|
22876
|
+
total_market_alone_upper_usd: totalMarket,
|
|
22735
22877
|
savvly_above_market_upper_usd: upside,
|
|
22736
22878
|
early_exit_refund_usd: refund,
|
|
22737
22879
|
percentage_gain_upper_percent: pctGain,
|
|
@@ -22743,19 +22885,67 @@ function payoutPayload(inputs, result, depositType, deposited) {
|
|
|
22743
22885
|
inputs,
|
|
22744
22886
|
result,
|
|
22745
22887
|
summary: summarizePayouts(result, depositType, deposited),
|
|
22888
|
+
disclosure: disclosureBlock(),
|
|
22746
22889
|
metadata: {
|
|
22747
22890
|
disclaimer: disclaimerBlock(),
|
|
22891
|
+
methodology: SAVVLY_VS_MARKET_ALONE_PAYOUT_METHODOLOGY,
|
|
22748
22892
|
field_descriptions: PAYOUT_FIELD_DESCRIPTIONS
|
|
22749
22893
|
}
|
|
22750
22894
|
};
|
|
22751
22895
|
}
|
|
22896
|
+
var RETIREMENT_DISPLAY_HINTS = {
|
|
22897
|
+
chart: {
|
|
22898
|
+
type: "area",
|
|
22899
|
+
reference: "https://estimator.savvly.com \u2014 Retirement Forecast chart",
|
|
22900
|
+
x_axis: { field: "age", label: "Age" },
|
|
22901
|
+
y_axis: { label: "Balance (USD)", format: "currency_usd" },
|
|
22902
|
+
series: [
|
|
22903
|
+
{
|
|
22904
|
+
field: "retirement_savings_without_savvly_in_the_portfolio",
|
|
22905
|
+
label: "Current Retirement Forecast",
|
|
22906
|
+
color: "#9CA3AF",
|
|
22907
|
+
fill: "solid"
|
|
22908
|
+
},
|
|
22909
|
+
{
|
|
22910
|
+
field: "retirement_savings_with_savvly_in_the_portfolio",
|
|
22911
|
+
label: "With a longevity solution in the portfolio",
|
|
22912
|
+
color: "#3B82F6",
|
|
22913
|
+
fill: "solid"
|
|
22914
|
+
}
|
|
22915
|
+
],
|
|
22916
|
+
interpolation: "linear",
|
|
22917
|
+
show_markers: false,
|
|
22918
|
+
tooltip: {
|
|
22919
|
+
age: "Age",
|
|
22920
|
+
retirement_savings_without_savvly_in_the_portfolio: "Current Retirement Forecast",
|
|
22921
|
+
retirement_savings_with_savvly_in_the_portfolio: "With a longevity solution in the portfolio",
|
|
22922
|
+
difference_between_retirement_savings_with_and_without_savvly_in_the_portfolio: "Difference"
|
|
22923
|
+
}
|
|
22924
|
+
},
|
|
22925
|
+
layout: {
|
|
22926
|
+
above_chart: "kpi_cards",
|
|
22927
|
+
below_chart: "narrative"
|
|
22928
|
+
},
|
|
22929
|
+
rendering_notes: [
|
|
22930
|
+
"Plot `age_dependent_values` rows exactly as returned \u2014 one row per year. Savvly milestone payouts at ages 80/85/90/95 must appear as sharp vertical spikes on the with-Savvly series; year-by-year monthly-paycheck withdrawals show as the depletion between spikes. Do NOT smooth/spline the line \u2014 smoothing rounds the spikes off into bumps and misrepresents the payout schedule.",
|
|
22931
|
+
"Render the without-Savvly series exactly as returned. It drops to 0 once the traditional portfolio is exhausted; do not extrapolate past that.",
|
|
22932
|
+
"If `monthly_paycheck` was omitted (default 0), neither series will deplete and the chart is a pure accumulation curve \u2014 surface that caveat to the user."
|
|
22933
|
+
]
|
|
22934
|
+
};
|
|
22935
|
+
function disclosureNarrative(kind) {
|
|
22936
|
+
const lead = kind === "retirement" ? "Hypothetical retirement projection \u2014 projections are based on assumed rates of return and are not guarantees of future performance." : "Hypothetical illustration of Savvly Longevity Benefit growth across the payout ages \u2014 not a guarantee of future performance.";
|
|
22937
|
+
return `${lead} See full disclosures at ${PRODUCT.disclaimers.full_disclosures_url}.`;
|
|
22938
|
+
}
|
|
22752
22939
|
function retirementPayload(inputs, result) {
|
|
22753
22940
|
return {
|
|
22754
22941
|
inputs,
|
|
22755
22942
|
result,
|
|
22943
|
+
summary: { narrative: disclosureNarrative("retirement") },
|
|
22944
|
+
disclosure: disclosureBlock(),
|
|
22756
22945
|
metadata: {
|
|
22757
22946
|
disclaimer: disclaimerBlock(),
|
|
22758
|
-
field_descriptions: RETIREMENT_FIELD_DESCRIPTIONS
|
|
22947
|
+
field_descriptions: RETIREMENT_FIELD_DESCRIPTIONS,
|
|
22948
|
+
display_hints: RETIREMENT_DISPLAY_HINTS
|
|
22759
22949
|
}
|
|
22760
22950
|
};
|
|
22761
22951
|
}
|
|
@@ -22913,7 +23103,7 @@ function searchQaLibrary(opts) {
|
|
|
22913
23103
|
function createMcpServer() {
|
|
22914
23104
|
const server = new McpServer({
|
|
22915
23105
|
name: "savvly",
|
|
22916
|
-
version:
|
|
23106
|
+
version: SERVER_VERSION
|
|
22917
23107
|
});
|
|
22918
23108
|
server.resource(
|
|
22919
23109
|
"product-overview",
|
|
@@ -23026,7 +23216,7 @@ function createMcpServer() {
|
|
|
23026
23216
|
"project_savvly_lumpsum",
|
|
23027
23217
|
{
|
|
23028
23218
|
title: "Project Savvly Lump-Sum Investment",
|
|
23029
|
-
description: "Retirement projection for a lump-sum investment in Savvly's Longevity Benefit Fund. Returns payout amounts at each milestone age (80, 85, 90, 95) with
|
|
23219
|
+
description: "Retirement projection for a lump-sum investment in Savvly's Longevity Benefit Fund. Returns payout amounts at each milestone age (80, 85, 90, 95) with Savvly vs market alone cumulative totals, per-age breakdowns, and server-provided `_lower`/`_upper` range bounds. Use `_upper` as the central illustrative estimate and `_lower` to communicate downside. Suitable for retirement income planning, annuity alternative analysis, and longevity benefit illustration. Response embeds SEC-style disclaimers and per-field interpretation hints under `metadata`. " + SAVVLY_VS_MARKET_ALONE_PAYOUT_METHODOLOGY + " " + DISCLOSURE_REQUIRED_TERSE,
|
|
23030
23220
|
inputSchema: {
|
|
23031
23221
|
current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
|
|
23032
23222
|
funding_amount: external_exports.number().min(100).describe("Lump sum investment in USD"),
|
|
@@ -23050,7 +23240,7 @@ function createMcpServer() {
|
|
|
23050
23240
|
"project_savvly_monthly",
|
|
23051
23241
|
{
|
|
23052
23242
|
title: "Project Savvly Monthly Contributions",
|
|
23053
|
-
description: "Retirement projection for monthly contributions to Savvly's Longevity Benefit Fund over a number of years. Returns payout amounts at milestone ages 80/85/90/95 with
|
|
23243
|
+
description: "Retirement projection for monthly contributions to Savvly's Longevity Benefit Fund over a number of years. Returns payout amounts at milestone ages 80/85/90/95 with Savvly vs market alone cumulative totals, per-age breakdowns, and server-provided `_lower`/`_upper` range bounds. Use `_upper` as the central illustrative estimate and `_lower` to communicate downside. Suitable for retirement savings planning, annuity alternative comparison, and longevity benefit illustration. Supports an optional annual contribution increase and an optional early-withdrawal age. Disclaimers + per-field hints under `metadata`. " + SAVVLY_VS_MARKET_ALONE_PAYOUT_METHODOLOGY + " " + DISCLOSURE_REQUIRED_TERSE,
|
|
23054
23244
|
inputSchema: {
|
|
23055
23245
|
current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
|
|
23056
23246
|
monthly_amount: external_exports.number().min(10).describe("Monthly contribution in USD"),
|
|
@@ -23094,7 +23284,7 @@ function createMcpServer() {
|
|
|
23094
23284
|
"project_retirement_with_savvly",
|
|
23095
23285
|
{
|
|
23096
23286
|
title: "Project Retirement Trajectory With Savvly",
|
|
23097
|
-
description: "Full retirement simulation showing the projected savings trajectory WITH and WITHOUT a Savvly allocation across the planning horizon (current_age \u2192 life_expectancy). Returns `gap_score`, `possible_higher_monthly_paycheck`, a server-provided headline message, and a per-year `age_dependent_values[]` timeline. Disclaimers + per-field hints under `metadata`.",
|
|
23287
|
+
description: "Full retirement simulation showing the projected savings trajectory WITH and WITHOUT a Savvly allocation across the planning horizon (current_age \u2192 life_expectancy). Returns `gap_score`, `possible_higher_monthly_paycheck`, a server-provided headline message, and a per-year `age_dependent_values[]` timeline. Disclaimers + per-field hints under `metadata`. " + DISCLOSURE_REQUIRED_TERSE,
|
|
23098
23288
|
inputSchema: {
|
|
23099
23289
|
current_age: external_exports.number().int().min(25).max(79).describe("Current age"),
|
|
23100
23290
|
retirement_age: external_exports.number().int().min(50).max(80).describe("Planned retirement age"),
|