@savvly/mcp-server 1.0.1 → 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 +240 -81
- 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
|
}
|
|
@@ -22435,42 +22525,42 @@ var PayoutAgeRowSchema = external_exports.object({
|
|
|
22435
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."
|
|
22436
22526
|
),
|
|
22437
22527
|
savvly_payout_lower: external_exports.number().describe(
|
|
22438
|
-
"USD. Lower bound of the modeled payout
|
|
22528
|
+
"USD. Lower bound of the modeled payout with Savvly at this milestone age."
|
|
22439
22529
|
),
|
|
22440
22530
|
savvly_payout_upper: external_exports.number().describe(
|
|
22441
|
-
"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."
|
|
22442
22532
|
),
|
|
22443
|
-
|
|
22444
|
-
"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."
|
|
22445
22535
|
),
|
|
22446
|
-
|
|
22447
|
-
"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."
|
|
22448
22538
|
),
|
|
22449
22539
|
savvly_upside_lower: external_exports.number().describe(
|
|
22450
|
-
"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)."
|
|
22451
22541
|
),
|
|
22452
22542
|
savvly_upside_upper: external_exports.number().describe(
|
|
22453
|
-
"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."
|
|
22454
22544
|
)
|
|
22455
22545
|
});
|
|
22456
22546
|
var PayoutEnvelopeSchema = external_exports.object({
|
|
22457
22547
|
total_savvly_upside_lower: external_exports.number().describe(
|
|
22458
|
-
"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."
|
|
22459
22549
|
),
|
|
22460
22550
|
total_savvly_upside_upper: external_exports.number().describe(
|
|
22461
|
-
"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`."
|
|
22462
22552
|
),
|
|
22463
22553
|
total_savvly_lower: external_exports.number().describe(
|
|
22464
|
-
"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."
|
|
22465
22555
|
),
|
|
22466
22556
|
total_savvly_upper: external_exports.number().describe(
|
|
22467
|
-
"USD. Upper bound of the total cumulative payout
|
|
22557
|
+
"USD. Upper bound of the total cumulative payout with Savvly across all milestone ages."
|
|
22468
22558
|
),
|
|
22469
|
-
|
|
22470
|
-
"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."
|
|
22471
22561
|
),
|
|
22472
|
-
|
|
22473
|
-
"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."
|
|
22474
22564
|
),
|
|
22475
22565
|
early_withdrawal_value: external_exports.number().describe(
|
|
22476
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."
|
|
@@ -22488,7 +22578,7 @@ var PayoutEnvelopeSchema = external_exports.object({
|
|
|
22488
22578
|
"Percent. Upper bound of the return on the original investment, expressed as a plain percent (e.g. 1710 = 1,710%)."
|
|
22489
22579
|
),
|
|
22490
22580
|
payout_age_dependent_values: external_exports.array(PayoutAgeRowSchema).describe(
|
|
22491
|
-
"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."
|
|
22492
22582
|
)
|
|
22493
22583
|
});
|
|
22494
22584
|
var RetirementAgeRowSchema = external_exports.object({
|
|
@@ -22557,9 +22647,9 @@ var ProjectionSummarySchema = external_exports.object({
|
|
|
22557
22647
|
"USD. Total amount deposited into Savvly. For single deposits this equals `funding_amount`. For monthly installments it equals `monthly_installment * installment_years * 12`."
|
|
22558
22648
|
),
|
|
22559
22649
|
total_savvly_upper_usd: external_exports.number().nullable().describe("Convenience copy of `result.total_savvly_upper`."),
|
|
22560
|
-
|
|
22650
|
+
total_market_alone_upper_usd: external_exports.number().nullable().describe("Convenience copy of `result.total_market_alone_upper`."),
|
|
22561
22651
|
savvly_above_market_upper_usd: external_exports.number().nullable().describe(
|
|
22562
|
-
"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."
|
|
22563
22653
|
),
|
|
22564
22654
|
early_exit_refund_usd: external_exports.number().nullable().describe("Convenience copy of `result.early_withdrawal_value`."),
|
|
22565
22655
|
percentage_gain_upper_percent: external_exports.number().nullable().describe("Convenience copy of `result.percentage_gain_upper`."),
|
|
@@ -22585,6 +22675,32 @@ var ProjectionResponseSchema = external_exports.object({
|
|
|
22585
22675
|
)
|
|
22586
22676
|
})
|
|
22587
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
|
+
);
|
|
22588
22704
|
var RetirementResponseSchema = external_exports.object({
|
|
22589
22705
|
inputs: external_exports.record(external_exports.unknown()),
|
|
22590
22706
|
result: RetirementEnvelopeSchema,
|
|
@@ -22600,16 +22716,17 @@ var RetirementResponseSchema = external_exports.object({
|
|
|
22600
22716
|
),
|
|
22601
22717
|
metadata: external_exports.object({
|
|
22602
22718
|
disclaimer: DisclaimerMetadataSchema,
|
|
22603
|
-
field_descriptions: external_exports.record(external_exports.string())
|
|
22719
|
+
field_descriptions: external_exports.record(external_exports.string()),
|
|
22720
|
+
display_hints: DisplayHintsSchema
|
|
22604
22721
|
})
|
|
22605
22722
|
});
|
|
22606
22723
|
var PAYOUT_FIELD_DESCRIPTIONS = {
|
|
22607
|
-
total_savvly_upside_lower: "USD. Lower bound of cumulative Savvly
|
|
22608
|
-
total_savvly_upside_upper: "USD. Upper bound (central illustrative) of cumulative Savvly
|
|
22609
|
-
total_savvly_lower: "USD. Lower bound of total cumulative payout
|
|
22610
|
-
total_savvly_upper: "USD. Upper bound of total cumulative payout
|
|
22611
|
-
|
|
22612
|
-
|
|
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.",
|
|
22613
22730
|
early_withdrawal_value: "USD. Refund if investor exits at `withdrawal_age`. Point estimate (no range).",
|
|
22614
22731
|
total_payout_at_withdrawal_age_lower: "USD. Lower bound of total payout if exiting at `withdrawal_age`.",
|
|
22615
22732
|
total_payout_at_withdrawal_age_upper: "USD. Upper bound of total payout if exiting at `withdrawal_age`.",
|
|
@@ -22617,12 +22734,12 @@ var PAYOUT_FIELD_DESCRIPTIONS = {
|
|
|
22617
22734
|
percentage_gain_upper: "Percent. Upper bound of return on original investment.",
|
|
22618
22735
|
payout_age_dependent_values: "Array. Per-milestone-age breakdown (ages 80/85/90/95). See payout_age_dependent_values[] field descriptions.",
|
|
22619
22736
|
"payout_age_dependent_values[].payout_age": "Years. Milestone age this row reports.",
|
|
22620
|
-
"payout_age_dependent_values[].savvly_payout_lower": "USD. Lower bound payout
|
|
22621
|
-
"payout_age_dependent_values[].savvly_payout_upper": "USD. Upper bound payout
|
|
22622
|
-
"payout_age_dependent_values[].
|
|
22623
|
-
"payout_age_dependent_values[].
|
|
22624
|
-
"payout_age_dependent_values[].savvly_upside_lower": "USD. Lower bound for Savvly
|
|
22625
|
-
"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."
|
|
22626
22743
|
};
|
|
22627
22744
|
var RETIREMENT_FIELD_DESCRIPTIONS = {
|
|
22628
22745
|
gap_score: "Server-computed score summarizing the savings-vs-paycheck gap.",
|
|
@@ -22743,19 +22860,20 @@ function disclosureBlock() {
|
|
|
22743
22860
|
};
|
|
22744
22861
|
}
|
|
22745
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.";
|
|
22746
22864
|
function summarizePayouts(envelope, depositType, deposited) {
|
|
22747
22865
|
const totalSavvly = envelope.total_savvly_upper ?? null;
|
|
22748
|
-
const totalMarket = envelope.
|
|
22866
|
+
const totalMarket = envelope.total_market_alone_upper ?? null;
|
|
22749
22867
|
const upside = totalSavvly !== null && totalMarket !== null ? totalSavvly - totalMarket : null;
|
|
22750
22868
|
const refund = envelope.early_withdrawal_value ?? null;
|
|
22751
22869
|
const pctGain = envelope.percentage_gain_upper ?? null;
|
|
22752
22870
|
const depositText = depositType === "single" ? `a single upfront deposit of ${formatUsd(deposited)}` : `monthly deposits totaling ${formatUsd(deposited)}`;
|
|
22753
|
-
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}.`;
|
|
22754
22872
|
return {
|
|
22755
22873
|
deposit_type: depositType,
|
|
22756
22874
|
deposited_amount_usd: deposited,
|
|
22757
22875
|
total_savvly_upper_usd: totalSavvly,
|
|
22758
|
-
|
|
22876
|
+
total_market_alone_upper_usd: totalMarket,
|
|
22759
22877
|
savvly_above_market_upper_usd: upside,
|
|
22760
22878
|
early_exit_refund_usd: refund,
|
|
22761
22879
|
percentage_gain_upper_percent: pctGain,
|
|
@@ -22770,10 +22888,50 @@ function payoutPayload(inputs, result, depositType, deposited) {
|
|
|
22770
22888
|
disclosure: disclosureBlock(),
|
|
22771
22889
|
metadata: {
|
|
22772
22890
|
disclaimer: disclaimerBlock(),
|
|
22891
|
+
methodology: SAVVLY_VS_MARKET_ALONE_PAYOUT_METHODOLOGY,
|
|
22773
22892
|
field_descriptions: PAYOUT_FIELD_DESCRIPTIONS
|
|
22774
22893
|
}
|
|
22775
22894
|
};
|
|
22776
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
|
+
};
|
|
22777
22935
|
function disclosureNarrative(kind) {
|
|
22778
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.";
|
|
22779
22937
|
return `${lead} See full disclosures at ${PRODUCT.disclaimers.full_disclosures_url}.`;
|
|
@@ -22786,7 +22944,8 @@ function retirementPayload(inputs, result) {
|
|
|
22786
22944
|
disclosure: disclosureBlock(),
|
|
22787
22945
|
metadata: {
|
|
22788
22946
|
disclaimer: disclaimerBlock(),
|
|
22789
|
-
field_descriptions: RETIREMENT_FIELD_DESCRIPTIONS
|
|
22947
|
+
field_descriptions: RETIREMENT_FIELD_DESCRIPTIONS,
|
|
22948
|
+
display_hints: RETIREMENT_DISPLAY_HINTS
|
|
22790
22949
|
}
|
|
22791
22950
|
};
|
|
22792
22951
|
}
|
|
@@ -22944,7 +23103,7 @@ function searchQaLibrary(opts) {
|
|
|
22944
23103
|
function createMcpServer() {
|
|
22945
23104
|
const server = new McpServer({
|
|
22946
23105
|
name: "savvly",
|
|
22947
|
-
version:
|
|
23106
|
+
version: SERVER_VERSION
|
|
22948
23107
|
});
|
|
22949
23108
|
server.resource(
|
|
22950
23109
|
"product-overview",
|
|
@@ -23057,7 +23216,7 @@ function createMcpServer() {
|
|
|
23057
23216
|
"project_savvly_lumpsum",
|
|
23058
23217
|
{
|
|
23059
23218
|
title: "Project Savvly Lump-Sum Investment",
|
|
23060
|
-
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,
|
|
23061
23220
|
inputSchema: {
|
|
23062
23221
|
current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
|
|
23063
23222
|
funding_amount: external_exports.number().min(100).describe("Lump sum investment in USD"),
|
|
@@ -23081,7 +23240,7 @@ function createMcpServer() {
|
|
|
23081
23240
|
"project_savvly_monthly",
|
|
23082
23241
|
{
|
|
23083
23242
|
title: "Project Savvly Monthly Contributions",
|
|
23084
|
-
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,
|
|
23085
23244
|
inputSchema: {
|
|
23086
23245
|
current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
|
|
23087
23246
|
monthly_amount: external_exports.number().min(10).describe("Monthly contribution in USD"),
|