@savvly/mcp-server 1.0.0 → 1.0.1

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/dist/cli.js CHANGED
@@ -22333,17 +22333,9 @@ var AdvisorClientError = class extends Error {
22333
22333
  status;
22334
22334
  responseBody;
22335
22335
  };
22336
- var AdvisorNotConfigured = class extends Error {
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
- };
22336
+ var DEFAULT_ADVISOR_FUNCTIONS_URL = "https://savvly-estimator.azurewebsites.net";
22344
22337
  function getBaseUrl() {
22345
- const url = process.env.ADVISOR_FUNCTIONS_URL;
22346
- if (!url) throw new AdvisorNotConfigured();
22338
+ const url = process.env.ADVISOR_FUNCTIONS_URL ?? DEFAULT_ADVISOR_FUNCTIONS_URL;
22347
22339
  const trimmed = url.replace(/\/+$/, "");
22348
22340
  return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
22349
22341
  }
@@ -22529,6 +22521,17 @@ var RetirementEnvelopeSchema = external_exports.object({
22529
22521
  "Per-age timeline across the planning horizon (current_age \u2192 life_expectancy). One row per year."
22530
22522
  )
22531
22523
  });
22524
+ var TopLevelDisclosureSchema = external_exports.object({
22525
+ required: external_exports.literal(true).describe(
22526
+ "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."
22527
+ ),
22528
+ text: external_exports.string().describe(
22529
+ "Short verbatim SEC disclosure text. Display this string to the user alongside any projection figures from this response. Do not paraphrase, summarize, or omit."
22530
+ ),
22531
+ url: external_exports.string().describe(
22532
+ "Canonical full-disclosures URL. Render as a clickable link to the user alongside the `text`. Required by SEC Marketing Rule and FINRA Rule 2210."
22533
+ )
22534
+ });
22532
22535
  var DisclaimerMetadataSchema = external_exports.object({
22533
22536
  summary: external_exports.string().describe(
22534
22537
  "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."
@@ -22561,7 +22564,7 @@ var ProjectionSummarySchema = external_exports.object({
22561
22564
  early_exit_refund_usd: external_exports.number().nullable().describe("Convenience copy of `result.early_withdrawal_value`."),
22562
22565
  percentage_gain_upper_percent: external_exports.number().nullable().describe("Convenience copy of `result.percentage_gain_upper`."),
22563
22566
  narrative: external_exports.string().describe(
22564
- "Human-readable English sentence summarizing the projection. Always ends with the phrase 'Payouts are illustrations, not guarantees.'"
22567
+ "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
22568
  )
22566
22569
  });
22567
22570
  var ProjectionResponseSchema = external_exports.object({
@@ -22572,6 +22575,9 @@ var ProjectionResponseSchema = external_exports.object({
22572
22575
  summary: ProjectionSummarySchema.describe(
22573
22576
  "Convenience summary including a human-readable narrative."
22574
22577
  ),
22578
+ disclosure: TopLevelDisclosureSchema.describe(
22579
+ "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."
22580
+ ),
22575
22581
  metadata: external_exports.object({
22576
22582
  disclaimer: DisclaimerMetadataSchema,
22577
22583
  field_descriptions: external_exports.record(external_exports.string()).describe(
@@ -22582,6 +22588,16 @@ var ProjectionResponseSchema = external_exports.object({
22582
22588
  var RetirementResponseSchema = external_exports.object({
22583
22589
  inputs: external_exports.record(external_exports.unknown()),
22584
22590
  result: RetirementEnvelopeSchema,
22591
+ summary: external_exports.object({
22592
+ narrative: external_exports.string().describe(
22593
+ "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."
22594
+ )
22595
+ }).describe(
22596
+ "Convenience summary. The narrative carries the canonical full-disclosures URL inline; display it verbatim alongside any figures from this response."
22597
+ ),
22598
+ disclosure: TopLevelDisclosureSchema.describe(
22599
+ "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."
22600
+ ),
22585
22601
  metadata: external_exports.object({
22586
22602
  disclaimer: DisclaimerMetadataSchema,
22587
22603
  field_descriptions: external_exports.record(external_exports.string())
@@ -22719,6 +22735,14 @@ function disclaimerBlock() {
22719
22735
  full_disclosures_url: PRODUCT.disclaimers.full_disclosures_url
22720
22736
  };
22721
22737
  }
22738
+ function disclosureBlock() {
22739
+ return {
22740
+ required: true,
22741
+ text: PRODUCT.disclaimers.projections,
22742
+ url: PRODUCT.disclaimers.full_disclosures_url
22743
+ };
22744
+ }
22745
+ 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.";
22722
22746
  function summarizePayouts(envelope, depositType, deposited) {
22723
22747
  const totalSavvly = envelope.total_savvly_upper ?? null;
22724
22748
  const totalMarket = envelope.total_without_savvly_upper ?? null;
@@ -22726,7 +22750,7 @@ function summarizePayouts(envelope, depositType, deposited) {
22726
22750
  const refund = envelope.early_withdrawal_value ?? null;
22727
22751
  const pctGain = envelope.percentage_gain_upper ?? null;
22728
22752
  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 markets 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.";
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 markets 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
22754
  return {
22731
22755
  deposit_type: depositType,
22732
22756
  deposited_amount_usd: deposited,
@@ -22743,16 +22767,23 @@ function payoutPayload(inputs, result, depositType, deposited) {
22743
22767
  inputs,
22744
22768
  result,
22745
22769
  summary: summarizePayouts(result, depositType, deposited),
22770
+ disclosure: disclosureBlock(),
22746
22771
  metadata: {
22747
22772
  disclaimer: disclaimerBlock(),
22748
22773
  field_descriptions: PAYOUT_FIELD_DESCRIPTIONS
22749
22774
  }
22750
22775
  };
22751
22776
  }
22777
+ function disclosureNarrative(kind) {
22778
+ 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
+ return `${lead} See full disclosures at ${PRODUCT.disclaimers.full_disclosures_url}.`;
22780
+ }
22752
22781
  function retirementPayload(inputs, result) {
22753
22782
  return {
22754
22783
  inputs,
22755
22784
  result,
22785
+ summary: { narrative: disclosureNarrative("retirement") },
22786
+ disclosure: disclosureBlock(),
22756
22787
  metadata: {
22757
22788
  disclaimer: disclaimerBlock(),
22758
22789
  field_descriptions: RETIREMENT_FIELD_DESCRIPTIONS
@@ -22913,7 +22944,7 @@ function searchQaLibrary(opts) {
22913
22944
  function createMcpServer() {
22914
22945
  const server = new McpServer({
22915
22946
  name: "savvly",
22916
- version: "1.0.0"
22947
+ version: "1.0.1"
22917
22948
  });
22918
22949
  server.resource(
22919
22950
  "product-overview",
@@ -23026,7 +23057,7 @@ function createMcpServer() {
23026
23057
  "project_savvly_lumpsum",
23027
23058
  {
23028
23059
  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 WITH-Savvly vs WITHOUT-Savvly 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`.",
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 WITH-Savvly vs WITHOUT-Savvly 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`. " + DISCLOSURE_REQUIRED_TERSE,
23030
23061
  inputSchema: {
23031
23062
  current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
23032
23063
  funding_amount: external_exports.number().min(100).describe("Lump sum investment in USD"),
@@ -23050,7 +23081,7 @@ function createMcpServer() {
23050
23081
  "project_savvly_monthly",
23051
23082
  {
23052
23083
  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 WITH-Savvly vs WITHOUT-Savvly 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`.",
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 WITH-Savvly vs WITHOUT-Savvly 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`. " + DISCLOSURE_REQUIRED_TERSE,
23054
23085
  inputSchema: {
23055
23086
  current_age: external_exports.number().int().min(25).max(79).describe("Investor's current age"),
23056
23087
  monthly_amount: external_exports.number().min(10).describe("Monthly contribution in USD"),
@@ -23094,7 +23125,7 @@ function createMcpServer() {
23094
23125
  "project_retirement_with_savvly",
23095
23126
  {
23096
23127
  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`.",
23128
+ 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
23129
  inputSchema: {
23099
23130
  current_age: external_exports.number().int().min(25).max(79).describe("Current age"),
23100
23131
  retirement_age: external_exports.number().int().min(50).max(80).describe("Planned retirement age"),