@nestpilot/mcp-app 1.0.0

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.
Files changed (149) hide show
  1. package/README.md +350 -0
  2. package/dist/cli/doctor.d.ts +1 -0
  3. package/dist/cli/doctor.js +214 -0
  4. package/dist/cli/export-import.d.ts +6 -0
  5. package/dist/cli/export-import.js +132 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.js +168 -0
  8. package/dist/cli/init.d.ts +1 -0
  9. package/dist/cli/init.js +171 -0
  10. package/dist/host-configs/cowork.json +11 -0
  11. package/dist/host-configs/goose.yaml +22 -0
  12. package/dist/host-configs/openclaw-manifest.json +16 -0
  13. package/dist/main.d.ts +2 -0
  14. package/dist/main.js +128 -0
  15. package/dist/mcp-app.html +155 -0
  16. package/dist/nestpilot-client.d.ts +44 -0
  17. package/dist/nestpilot-client.js +160 -0
  18. package/dist/planner.html +222 -0
  19. package/dist/server.d.ts +19 -0
  20. package/dist/server.js +245 -0
  21. package/dist/skills/SKILL.md +162 -0
  22. package/dist/skills/manifest.json +51 -0
  23. package/dist/skills/tools/activate_plan.md +36 -0
  24. package/dist/skills/tools/coach.md +59 -0
  25. package/dist/skills/tools/comprehensive_plan.md +65 -0
  26. package/dist/skills/tools/create_plan.md +59 -0
  27. package/dist/skills/tools/create_saved_plan.md +49 -0
  28. package/dist/skills/tools/delete_plan.md +42 -0
  29. package/dist/skills/tools/delete_scenario.md +38 -0
  30. package/dist/skills/tools/generate_proposal.md +63 -0
  31. package/dist/skills/tools/generate_retirement_report.md +50 -0
  32. package/dist/skills/tools/get_active_plan.md +44 -0
  33. package/dist/skills/tools/get_baseline_forecast.md +47 -0
  34. package/dist/skills/tools/get_plan.md +44 -0
  35. package/dist/skills/tools/get_plan_components.md +50 -0
  36. package/dist/skills/tools/get_scenario.md +46 -0
  37. package/dist/skills/tools/list_plans.md +44 -0
  38. package/dist/skills/tools/list_scenarios.md +42 -0
  39. package/dist/skills/tools/medicare-guardian.md +59 -0
  40. package/dist/skills/tools/nestpilot_run_plan.md +61 -0
  41. package/dist/skills/tools/optimize_roth_conversion.md +107 -0
  42. package/dist/skills/tools/optimize_ss_claiming.md +30 -0
  43. package/dist/skills/tools/rename_plan.md +34 -0
  44. package/dist/skills/tools/retirement-planner.md +55 -0
  45. package/dist/skills/tools/run_forecast.md +65 -0
  46. package/dist/skills/tools/run_saved_forecast.md +52 -0
  47. package/dist/skills/tools/run_scenario.md +66 -0
  48. package/dist/skills/tools/save_plan.md +48 -0
  49. package/dist/skills/tools/save_scenario.md +50 -0
  50. package/dist/skills/tools/verify_forecast.md +43 -0
  51. package/dist/src/config.d.ts +20 -0
  52. package/dist/src/config.js +44 -0
  53. package/dist/src/contracts/provenance.d.ts +37 -0
  54. package/dist/src/contracts/provenance.js +71 -0
  55. package/dist/src/contracts/tool-contract-registry.d.ts +43 -0
  56. package/dist/src/contracts/tool-contract-registry.js +282 -0
  57. package/dist/src/local/cloud-compute-client.d.ts +55 -0
  58. package/dist/src/local/cloud-compute-client.js +135 -0
  59. package/dist/src/local/encryption.d.ts +24 -0
  60. package/dist/src/local/encryption.js +105 -0
  61. package/dist/src/local/keychain.d.ts +41 -0
  62. package/dist/src/local/keychain.js +236 -0
  63. package/dist/src/local/local-config.d.ts +34 -0
  64. package/dist/src/local/local-config.js +61 -0
  65. package/dist/src/local/local-data-layer.d.ts +20 -0
  66. package/dist/src/local/local-data-layer.js +15 -0
  67. package/dist/src/local/local-plan-store.d.ts +66 -0
  68. package/dist/src/local/local-plan-store.js +195 -0
  69. package/dist/src/local/pii-scrubber.d.ts +26 -0
  70. package/dist/src/local/pii-scrubber.js +219 -0
  71. package/dist/src/policy/policy-engine.d.ts +44 -0
  72. package/dist/src/policy/policy-engine.js +119 -0
  73. package/dist/src/rate-limit.d.ts +17 -0
  74. package/dist/src/rate-limit.js +41 -0
  75. package/dist/src/security.d.ts +19 -0
  76. package/dist/src/security.js +118 -0
  77. package/dist/src/skills/index.d.ts +12 -0
  78. package/dist/src/skills/index.js +16 -0
  79. package/dist/src/skills/retirement-pack-v1.d.ts +28 -0
  80. package/dist/src/skills/retirement-pack-v1.js +295 -0
  81. package/dist/src/skills/skill-executor.d.ts +65 -0
  82. package/dist/src/skills/skill-executor.js +174 -0
  83. package/dist/src/skills/skill-manifest-schema.d.ts +337 -0
  84. package/dist/src/skills/skill-manifest-schema.js +94 -0
  85. package/dist/src/skills/skill-registry.d.ts +71 -0
  86. package/dist/src/skills/skill-registry.js +116 -0
  87. package/dist/src/telemetry.d.ts +12 -0
  88. package/dist/src/telemetry.js +59 -0
  89. package/dist/src/types.d.ts +46 -0
  90. package/dist/src/types.js +4 -0
  91. package/dist/tools/agent-tools.d.ts +12 -0
  92. package/dist/tools/agent-tools.js +141 -0
  93. package/dist/tools/forecast-management-tools.d.ts +9 -0
  94. package/dist/tools/forecast-management-tools.js +133 -0
  95. package/dist/tools/local-plan-tools.d.ts +8 -0
  96. package/dist/tools/local-plan-tools.js +357 -0
  97. package/dist/tools/mcp-helpers.d.ts +52 -0
  98. package/dist/tools/mcp-helpers.js +177 -0
  99. package/dist/tools/medicare-tools.d.ts +3 -0
  100. package/dist/tools/medicare-tools.js +162 -0
  101. package/dist/tools/optimize-roth-tools-test.d.ts +2 -0
  102. package/dist/tools/optimize-roth-tools-test.js +36 -0
  103. package/dist/tools/optimize-roth-tools.d.ts +3 -0
  104. package/dist/tools/optimize-roth-tools.js +818 -0
  105. package/dist/tools/plan-management-tools.d.ts +3 -0
  106. package/dist/tools/plan-management-tools.js +196 -0
  107. package/dist/tools/planning-tools.d.ts +3 -0
  108. package/dist/tools/planning-tools.js +290 -0
  109. package/dist/tools/proposal-tools.d.ts +3 -0
  110. package/dist/tools/proposal-tools.js +428 -0
  111. package/dist/tools/report-tools.d.ts +3 -0
  112. package/dist/tools/report-tools.js +245 -0
  113. package/dist/tools/scenario-management-tools.d.ts +3 -0
  114. package/dist/tools/scenario-management-tools.js +136 -0
  115. package/dist/views/verification-packet.html +211 -0
  116. package/host-configs/cowork.json +11 -0
  117. package/host-configs/goose.yaml +22 -0
  118. package/host-configs/openclaw-manifest.json +16 -0
  119. package/package.json +66 -0
  120. package/skills/SKILL.md +162 -0
  121. package/skills/manifest.json +51 -0
  122. package/skills/tools/activate_plan.md +36 -0
  123. package/skills/tools/coach.md +59 -0
  124. package/skills/tools/comprehensive_plan.md +65 -0
  125. package/skills/tools/create_plan.md +59 -0
  126. package/skills/tools/create_saved_plan.md +49 -0
  127. package/skills/tools/delete_plan.md +42 -0
  128. package/skills/tools/delete_scenario.md +38 -0
  129. package/skills/tools/generate_proposal.md +63 -0
  130. package/skills/tools/generate_retirement_report.md +50 -0
  131. package/skills/tools/get_active_plan.md +44 -0
  132. package/skills/tools/get_baseline_forecast.md +47 -0
  133. package/skills/tools/get_plan.md +44 -0
  134. package/skills/tools/get_plan_components.md +50 -0
  135. package/skills/tools/get_scenario.md +46 -0
  136. package/skills/tools/list_plans.md +44 -0
  137. package/skills/tools/list_scenarios.md +42 -0
  138. package/skills/tools/medicare-guardian.md +59 -0
  139. package/skills/tools/nestpilot_run_plan.md +61 -0
  140. package/skills/tools/optimize_roth_conversion.md +107 -0
  141. package/skills/tools/optimize_ss_claiming.md +30 -0
  142. package/skills/tools/rename_plan.md +34 -0
  143. package/skills/tools/retirement-planner.md +55 -0
  144. package/skills/tools/run_forecast.md +65 -0
  145. package/skills/tools/run_saved_forecast.md +52 -0
  146. package/skills/tools/run_scenario.md +66 -0
  147. package/skills/tools/save_plan.md +48 -0
  148. package/skills/tools/save_scenario.md +50 -0
  149. package/skills/tools/verify_forecast.md +43 -0
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type AuthContext } from "./mcp-helpers.js";
3
+ export declare function registerPlanManagementTools(server: McpServer, authCtx?: AuthContext): void;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Plan management MCP tools — CRUD operations on persisted plans.
3
+ *
4
+ * Mirrors the plan management API used by the web app (apps/web/hooks/useApi.ts):
5
+ * list_plans, get_plan, get_active_plan, create_saved_plan, save_plan,
6
+ * activate_plan, delete_plan, rename_plan, get_plan_components.
7
+ */
8
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
9
+ import { z } from "zod";
10
+ import { proxyGetTool, proxyPostTool, proxyPutTool, proxyDeleteTool, proxyPatchTool, } from "./mcp-helpers.js";
11
+ export function registerPlanManagementTools(server, authCtx) {
12
+ // ── 1. list_plans ───────────────────────────────────────────────────────
13
+ registerAppTool(server, "list_plans", {
14
+ title: "List Plans",
15
+ description: `Lists all retirement plans saved by the authenticated user.
16
+
17
+ USE THIS TOOL WHEN THE USER:
18
+ - Asks "What plans do I have?" or "Show me my plans"
19
+ - Wants to pick a plan to view or edit
20
+ - Needs plan IDs for other operations (forecast, scenario, etc.)
21
+
22
+ REQUIRES authentication — will return an empty list for anonymous users.`,
23
+ inputSchema: {},
24
+ _meta: { ui: { visibility: ["model", "app"] } },
25
+ }, async () => {
26
+ return proxyGetTool("/api/plans", undefined, {
27
+ toolName: "list_plans",
28
+ authCtx,
29
+ });
30
+ });
31
+ // ── 2. get_plan ─────────────────────────────────────────────────────────
32
+ registerAppTool(server, "get_plan", {
33
+ title: "Get Plan",
34
+ description: `Retrieves a single saved plan by its ID, including all inputs.
35
+
36
+ USE THIS TOOL WHEN THE USER:
37
+ - Wants to view the details of a specific plan
38
+ - Needs to load a plan before editing or running a forecast`,
39
+ inputSchema: {
40
+ planId: z.string().describe("UUID of the plan to retrieve"),
41
+ },
42
+ _meta: { ui: { visibility: ["model", "app"] } },
43
+ }, async (args) => {
44
+ const { planId } = args;
45
+ return proxyGetTool(`/api/plans/${planId}`, undefined, {
46
+ toolName: "get_plan",
47
+ authCtx,
48
+ });
49
+ });
50
+ // ── 3. get_active_plan ──────────────────────────────────────────────────
51
+ registerAppTool(server, "get_active_plan", {
52
+ title: "Get Active Plan",
53
+ description: `Retrieves the user's currently active plan.
54
+
55
+ USE THIS TOOL WHEN THE USER:
56
+ - Asks about "my plan" or "my current plan" without specifying which one
57
+ - Wants to work with their default/active plan
58
+ - Needs the active plan's ID for forecasts or scenarios`,
59
+ inputSchema: {},
60
+ _meta: { ui: { visibility: ["model", "app"] } },
61
+ }, async () => {
62
+ return proxyGetTool("/api/plans/active", undefined, {
63
+ toolName: "get_active_plan",
64
+ authCtx,
65
+ });
66
+ });
67
+ // ── 4. create_saved_plan ────────────────────────────────────────────────
68
+ registerAppTool(server, "create_saved_plan", {
69
+ title: "Create Saved Plan",
70
+ description: `Creates a new retirement plan and saves it to the database.
71
+
72
+ USE THIS TOOL WHEN THE USER:
73
+ - Wants to save a new plan after providing their financial details
74
+ - Says "save this plan" or "create a new plan called..."
75
+ - Has provided comprehensive plan data and wants it persisted
76
+
77
+ Unlike create_plan (which is a stateless calculation), this tool persists the
78
+ plan to the database so it can be retrieved, updated, and used for forecasts.
79
+
80
+ The inputs should match the PlanContract schema: household, goals, accounts,
81
+ incomeStreams, spendStreams, assumptions, strategy, preferences.`,
82
+ inputSchema: {
83
+ name: z.string().describe("Human-readable plan name"),
84
+ inputs: z
85
+ .record(z.any())
86
+ .describe("PlanContract object with all plan data (household, goals, accounts, incomeStreams, etc.)"),
87
+ },
88
+ _meta: { ui: { visibility: ["model", "app"] } },
89
+ }, async (args) => {
90
+ const { name, inputs } = args;
91
+ const endpoint = `/api/plans?name=${encodeURIComponent(name)}`;
92
+ return proxyPostTool(endpoint, inputs, {
93
+ toolName: "create_saved_plan",
94
+ authCtx,
95
+ });
96
+ });
97
+ // ── 5. save_plan ────────────────────────────────────────────────────────
98
+ registerAppTool(server, "save_plan", {
99
+ title: "Save Plan",
100
+ description: `Updates an existing saved plan with new inputs.
101
+
102
+ USE THIS TOOL WHEN THE USER:
103
+ - Has modified their plan and wants to save the changes
104
+ - Says "update my plan" after making changes
105
+ - Wants to overwrite a plan with new data`,
106
+ inputSchema: {
107
+ planId: z.string().describe("UUID of the plan to update"),
108
+ name: z.string().describe("Plan name"),
109
+ inputs: z
110
+ .record(z.any())
111
+ .describe("Updated PlanContract object"),
112
+ },
113
+ _meta: { ui: { visibility: ["model", "app"] } },
114
+ }, async (args) => {
115
+ const { planId, name, inputs } = args;
116
+ const endpoint = `/api/plans/${planId}?name=${encodeURIComponent(name)}`;
117
+ return proxyPutTool(endpoint, inputs, {
118
+ toolName: "save_plan",
119
+ authCtx,
120
+ });
121
+ });
122
+ // ── 6. activate_plan ────────────────────────────────────────────────────
123
+ registerAppTool(server, "activate_plan", {
124
+ title: "Activate Plan",
125
+ description: `Sets a plan as the user's active (default) plan.
126
+
127
+ USE THIS TOOL WHEN THE USER:
128
+ - Says "make this my active plan" or "switch to plan X"
129
+ - Wants to change which plan is used by default`,
130
+ inputSchema: {
131
+ planId: z.string().describe("UUID of the plan to activate"),
132
+ },
133
+ _meta: { ui: { visibility: ["model", "app"] } },
134
+ }, async (args) => {
135
+ const { planId } = args;
136
+ return proxyPostTool(`/api/plans/${planId}/activate`, {}, { toolName: "activate_plan", authCtx });
137
+ });
138
+ // ── 7. delete_plan ──────────────────────────────────────────────────────
139
+ registerAppTool(server, "delete_plan", {
140
+ title: "Delete Plan",
141
+ description: `Deletes a saved plan. This is a soft delete and may cascade to associated
142
+ forecasts and scenarios.
143
+
144
+ USE THIS TOOL WHEN THE USER:
145
+ - Wants to remove a plan they no longer need
146
+ - Says "delete this plan" or "remove plan X"`,
147
+ inputSchema: {
148
+ planId: z.string().describe("UUID of the plan to delete"),
149
+ },
150
+ _meta: { ui: { visibility: ["model", "app"] } },
151
+ }, async (args) => {
152
+ const { planId } = args;
153
+ return proxyDeleteTool(`/api/plans/${planId}`, {
154
+ toolName: "delete_plan",
155
+ authCtx,
156
+ });
157
+ });
158
+ // ── 8. rename_plan ──────────────────────────────────────────────────────
159
+ registerAppTool(server, "rename_plan", {
160
+ title: "Rename Plan",
161
+ description: `Renames an existing plan.
162
+
163
+ USE THIS TOOL WHEN THE USER:
164
+ - Wants to change the name of a plan
165
+ - Says "rename this plan to..." or "call it..."`,
166
+ inputSchema: {
167
+ planId: z.string().describe("UUID of the plan to rename"),
168
+ newName: z.string().describe("New name for the plan"),
169
+ },
170
+ _meta: { ui: { visibility: ["model", "app"] } },
171
+ }, async (args) => {
172
+ const { planId, newName } = args;
173
+ return proxyPatchTool(`/api/plans/${planId}`, { name: newName }, { toolName: "rename_plan", authCtx });
174
+ });
175
+ // ── 9. get_plan_components ──────────────────────────────────────────────
176
+ registerAppTool(server, "get_plan_components", {
177
+ title: "Get Plan Components",
178
+ description: `Retrieves the financial components attached to a saved plan
179
+ (annuities, pensions, rental income, insurance, etc.).
180
+
181
+ USE THIS TOOL WHEN THE USER:
182
+ - Asks what components are part of their plan
183
+ - Wants to see add-on financial products in their plan
184
+ - Needs component IDs for scenario overrides (componentOverrides in run_scenario)`,
185
+ inputSchema: {
186
+ planId: z.string().describe("UUID of the saved plan"),
187
+ },
188
+ _meta: { ui: { visibility: ["model", "app"] } },
189
+ }, async (args) => {
190
+ const { planId } = args;
191
+ return proxyGetTool(`/api/plans/${planId}/components`, undefined, {
192
+ toolName: "get_plan_components",
193
+ authCtx,
194
+ });
195
+ });
196
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type AuthContext } from "./mcp-helpers.js";
3
+ export declare function registerPlanningTools(server: McpServer, authCtx?: AuthContext): void;
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Retirement planning MCP tools — create_plan, run_forecast, run_scenario,
3
+ * verify_forecast, coach.
4
+ *
5
+ * Each tool is a thin facade over a NestPilot Spring Boot API endpoint.
6
+ * Registered via registerAppTool() so they're discoverable by MCP Apps hosts.
7
+ */
8
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
9
+ import { z } from "zod";
10
+ import { proxyPostTool } from "./mcp-helpers.js";
11
+ export function registerPlanningTools(server, authCtx) {
12
+ // ── 1. create_plan ────────────────────────────────────────────────────
13
+ // Quick retirement projection via POST /api/plan/calc
14
+ registerAppTool(server, "create_plan", {
15
+ title: "Create Retirement Plan",
16
+ description: `Creates a quick retirement plan projection. Returns projected balance at retirement, safe withdrawal amount, readiness score, and sustainability years.
17
+
18
+ USE THIS TOOL WHEN THE USER:
19
+ - Wants a quick retirement projection given basic inputs
20
+ - Asks "Am I on track to retire?" or "How much will I have at retirement?"
21
+ - Provides their age, retirement age, savings balance, and contribution amounts
22
+ - Wants to understand their withdrawal rate feasibility
23
+
24
+ DO NOT USE when the user has detailed accounts, income streams, or tax data — use run_forecast instead for comprehensive analysis.`,
25
+ inputSchema: {
26
+ currentAge: z
27
+ .number()
28
+ .int()
29
+ .min(0)
30
+ .describe("Current age of the user"),
31
+ retireAge: z
32
+ .number()
33
+ .int()
34
+ .min(0)
35
+ .describe("Target retirement age"),
36
+ lifeExpectancy: z
37
+ .number()
38
+ .int()
39
+ .min(0)
40
+ .describe("Planning horizon / life expectancy in years"),
41
+ currentBalance: z
42
+ .number()
43
+ .min(0)
44
+ .optional()
45
+ .describe("Current total retirement savings balance in dollars"),
46
+ monthlyContribution: z
47
+ .number()
48
+ .min(0)
49
+ .optional()
50
+ .describe("Monthly savings contribution in dollars"),
51
+ realReturnRate: z
52
+ .number()
53
+ .describe("Expected real annual return rate as decimal, e.g. 0.05 for 5%"),
54
+ withdrawalRate: z
55
+ .number()
56
+ .describe("Planned annual withdrawal rate in retirement as decimal, e.g. 0.04 for 4%"),
57
+ targetAnnualSpending: z
58
+ .number()
59
+ .optional()
60
+ .describe("Desired annual spending in retirement, in dollars"),
61
+ userId: z
62
+ .string()
63
+ .optional()
64
+ .describe("Optional user ID for auto-filling balance from linked accounts"),
65
+ },
66
+ _meta: {
67
+ ui: {
68
+ visibility: ["model", "app"],
69
+ },
70
+ },
71
+ }, async (args) => {
72
+ return proxyPostTool("/api/plan/calc", args, {
73
+ toolName: "create_plan",
74
+ authCtx,
75
+ });
76
+ });
77
+ // ── 2. run_forecast ───────────────────────────────────────────────────
78
+ // Comprehensive retirement forecast via POST /api/plan/forecast
79
+ registerAppTool(server, "run_forecast", {
80
+ title: "Run Retirement Forecast",
81
+ description: `Runs a comprehensive deterministic retirement forecast with multi-account, multi-income-stream support. Returns readiness score, yearly projections, portfolio runway, legacy value, and success probability.
82
+
83
+ USE THIS TOOL WHEN THE USER:
84
+ - Has detailed financial data: multiple accounts (401k, IRA, Roth, taxable), income streams, Social Security
85
+ - Wants year-by-year projections through retirement
86
+ - Asks about portfolio runway or "When will I run out of money?"
87
+ - Wants to consider tax optimization, withdrawal ordering, or Roth conversions
88
+ - Has a spouse and needs household-level analysis
89
+
90
+ REQUIRES at minimum: currentAge, retirementAge, targetMonthlySpending, at least one account, and at least one income stream.`,
91
+ inputSchema: {
92
+ payload: z
93
+ .record(z.any())
94
+ .describe(`Full PlanInputExtended object. Required fields: currentAge (int >=18), retirementAge (int >=50), horizonAge (int, default 95), targetMonthlySpending (number), accounts (array of {type, balance, annualContribution?, realReturn?} where type is one of: traditional, roth, taxable, hsa, cash — MUST be lowercase), incomeStreams (array of {type, amountMonthly, startAge?, endAge?} where type is one of: salary, social_security, pension, annuity, rental, other — MUST be lowercase). Optional: socialSecurityFRA, socialSecurityClaimAge, safeWithdrawalRate (default 0.04), effectiveTaxRate (default 0.15), withdrawalStrategy ("taxable_first"|"traditional_first"), filingStatus, spouse, inflationRate (default 0.03), displayMode ("real"|"nominal"), planId.`),
95
+ },
96
+ _meta: {
97
+ ui: {
98
+ visibility: ["model", "app"],
99
+ },
100
+ },
101
+ }, async (args) => {
102
+ const { payload } = args;
103
+ return proxyPostTool("/api/plan/forecast", payload, {
104
+ toolName: "run_forecast",
105
+ authCtx,
106
+ });
107
+ });
108
+ // ── 3. run_scenario ───────────────────────────────────────────────────
109
+ // What-if scenario analysis via POST /api/plan/scenario/run
110
+ registerAppTool(server, "run_scenario", {
111
+ title: "Run What-If Scenario",
112
+ description: `Runs a what-if scenario analysis comparing a baseline retirement plan against modified assumptions. Supports plan parameter deltas, component enable/disable overrides, and optional historical market stress tests. Returns baseline vs. candidate comparison with delta metrics.
113
+
114
+ USE THIS TOOL WHEN THE USER:
115
+ - Asks "What if I retire at 62 instead of 65?"
116
+ - Wants to compare scenarios (e.g., current plan vs. delaying Social Security)
117
+ - Asks about impact of enabling/disabling plan components (annuity, rental income)
118
+ - Wants to stress-test against historical market crashes (2008, dot-com, 1929)
119
+ - Says "How would my plan hold up in a recession?"
120
+
121
+ REQUIRES: A plan object and at least one override or delta.`,
122
+ inputSchema: {
123
+ plan: z
124
+ .record(z.any())
125
+ .describe(`PlanContract baseline plan object. Shape: {household: {primary: {age, retireAge, salary?, socialSecurity?}, spouse?}, goals: {retirement: {targetSpending: {amountMonthly}}}, accounts: [...], incomeStreams: [...], taxProfile?, assumptions?, strategy?}`),
126
+ label: z
127
+ .string()
128
+ .optional()
129
+ .describe("Human-readable label, e.g. 'Retire at 62' or 'Add annuity'"),
130
+ planId: z
131
+ .string()
132
+ .optional()
133
+ .describe("UUID of saved plan; used to load persisted components for override"),
134
+ overrides: z
135
+ .object({
136
+ planDeltas: z
137
+ .array(z.object({
138
+ path: z
139
+ .string()
140
+ .describe("JSON pointer to field, e.g. 'household.primary.retireAge'"),
141
+ to: z.any().describe("New value for the field"),
142
+ }))
143
+ .optional()
144
+ .describe("Field-level overrides for the candidate scenario"),
145
+ componentOverrides: z
146
+ .array(z.object({
147
+ componentId: z
148
+ .string()
149
+ .describe("UUID of plan component"),
150
+ enabled: z
151
+ .boolean()
152
+ .optional()
153
+ .describe("Override enabled state"),
154
+ paramsOverride: z
155
+ .record(z.any())
156
+ .optional()
157
+ .describe("Parameter overrides"),
158
+ }))
159
+ .optional()
160
+ .describe("Ephemeral component state changes"),
161
+ stressTest: z
162
+ .object({
163
+ scenarioId: z
164
+ .string()
165
+ .describe("e.g. '2008_CRISIS', '1929_CRASH', 'DOT_COM_BUST'"),
166
+ applyTo: z
167
+ .string()
168
+ .optional()
169
+ .describe("'EQUITY' or 'ALL'. Default: EQUITY"),
170
+ align: z
171
+ .string()
172
+ .optional()
173
+ .describe("'PLAN_START', 'RETIREMENT', or 'CUSTOM_YEAR'"),
174
+ customStartYear: z
175
+ .number()
176
+ .int()
177
+ .optional()
178
+ .describe("Only when align='CUSTOM_YEAR'"),
179
+ })
180
+ .optional()
181
+ .describe("Optional historical market stress test"),
182
+ })
183
+ .optional()
184
+ .describe("Bundle of overrides: plan deltas, component toggles, and/or stress test"),
185
+ },
186
+ _meta: {
187
+ ui: {
188
+ visibility: ["model", "app"],
189
+ },
190
+ },
191
+ }, async (args) => {
192
+ return proxyPostTool("/api/plan/scenario/run", args, { toolName: "run_scenario", authCtx });
193
+ });
194
+ // ── 4. verify_forecast ─────────────────────────────────────────────────
195
+ // Forecast verification via POST /api/plan/forecast/verify
196
+ registerAppTool(server, "verify_forecast", {
197
+ title: "Verify Forecast",
198
+ description: `Runs forecast verification and returns a verification packet — a structured, auditable record of the inputs, calculation trace, and verdict. Used to verify advisor projections or double-check personal planning scenarios.
199
+
200
+ USE THIS TOOL WHEN THE USER:
201
+ - Wants to verify numbers from their financial advisor
202
+ - Asks for a "second opinion" on a retirement projection
203
+ - Wants an auditable record of their retirement calculation
204
+ - Says "check my advisor's numbers" or "verify this projection"
205
+
206
+ DO NOT USE for new projections — use run_forecast instead. This tool is specifically for verification of existing projections.`,
207
+ inputSchema: {
208
+ payload: z
209
+ .record(z.any())
210
+ .describe(`PlanInputExtended payload for /api/plan/forecast/verify. Same shape as run_forecast: Required fields: currentAge (int >=18), retirementAge (int >=50), horizonAge (int, default 95), targetMonthlySpending (number), accounts (array of {type, balance, annualContribution?, realReturn?} where type MUST be lowercase: traditional, roth, taxable, hsa, cash), incomeStreams (array of {type, amountMonthly, startAge?, endAge?} where type MUST be lowercase: salary, social_security, pension, annuity, rental, other).`),
211
+ },
212
+ _meta: {
213
+ ui: {
214
+ resourceUri: "ui://verification-packet/verification-packet.html",
215
+ visibility: ["model", "app"],
216
+ },
217
+ },
218
+ }, async (args) => {
219
+ const { payload } = args;
220
+ return proxyPostTool("/api/plan/forecast/verify", payload, {
221
+ toolName: "verify_forecast",
222
+ authCtx,
223
+ });
224
+ });
225
+ // ── 5. coach ───────────────────────────────────────────────────────────
226
+ // AI coaching via POST /api/spring_ai/coach
227
+ registerAppTool(server, "coach", {
228
+ title: "Retirement Coach",
229
+ description: `Provides AI-powered retirement coaching — conversational guidance, explanations of retirement concepts, and personalized next-step suggestions.
230
+
231
+ USE THIS TOOL WHEN THE USER:
232
+ - Asks about retirement concepts ("What is a Roth conversion?", "How does the 4% rule work?")
233
+ - Wants personalized guidance after describing their situation
234
+ - Asks "What does this mean for me?" after running a forecast or scenario
235
+ - Has open-ended questions that need narrative answers, not calculations
236
+
237
+ DO NOT USE for calculations — use run_forecast or create_plan instead.
238
+ DO NOT USE for Medicare enrollment — use medicare-guardian instead.
239
+ DO NOT USE for verifying advisor numbers — use verify_forecast instead.`,
240
+ inputSchema: {
241
+ content: z
242
+ .string()
243
+ .describe("Prompt content for retirement coach guidance"),
244
+ userId: z.string().optional().describe("User ID for personalization"),
245
+ planId: z
246
+ .string()
247
+ .optional()
248
+ .describe("Plan ID for context-aware coaching"),
249
+ schemaVersion: z.string().optional().describe("Schema version"),
250
+ },
251
+ _meta: {
252
+ ui: {
253
+ visibility: ["model", "app"],
254
+ },
255
+ },
256
+ }, async (args) => {
257
+ return proxyPostTool("/api/spring_ai/coach", args, { toolName: "coach", authCtx });
258
+ });
259
+ // ── 6. comprehensive_plan ──────────────────────────────────────────────
260
+ // Combined coach insights + forecast in one call via POST /api/plan/comprehensive
261
+ registerAppTool(server, "comprehensive_plan", {
262
+ title: "Comprehensive Plan Analysis",
263
+ description: `Runs a combined coach-insights + forecast analysis in a single call.
264
+ Returns both coaching guidance and detailed forecast projections together.
265
+
266
+ USE THIS TOOL WHEN THE USER:
267
+ - Wants a full retirement analysis with both narrative insights and numbers
268
+ - Says "give me the full picture" or "analyze my plan comprehensively"
269
+ - Needs coach guidance alongside forecast projections in one response
270
+ - Wants to save time vs calling coach + run_forecast separately
271
+
272
+ REQUIRES the same PlanContract payload as run_forecast.`,
273
+ inputSchema: {
274
+ payload: z
275
+ .record(z.any())
276
+ .describe(`Full PlanContract object. Same shape as run_forecast payload: currentAge, retirementAge, horizonAge, targetMonthlySpending, accounts (array), incomeStreams (array). Optional: socialSecurityFRA, socialSecurityClaimAge, safeWithdrawalRate, effectiveTaxRate, withdrawalStrategy, filingStatus, spouse, inflationRate, displayMode, planId.`),
277
+ },
278
+ _meta: {
279
+ ui: {
280
+ visibility: ["model", "app"],
281
+ },
282
+ },
283
+ }, async (args) => {
284
+ const { payload } = args;
285
+ return proxyPostTool("/api/plan/comprehensive", payload, {
286
+ toolName: "comprehensive_plan",
287
+ authCtx,
288
+ });
289
+ });
290
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type AuthContext } from "./mcp-helpers.js";
3
+ export declare function registerProposalTools(server: McpServer, authCtx?: AuthContext): void;