@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,116 @@
1
+ /**
2
+ * Skill Registry — FEAT-0057
3
+ *
4
+ * Loads, validates, and indexes v1 skill manifests.
5
+ * Performs compatibility checks against FEAT-0056 tool contracts
6
+ * to ensure required tool versions are available before execution.
7
+ *
8
+ * @feature FEAT-0057
9
+ * @design DES-0055
10
+ */
11
+ import { validateManifest, } from "./skill-manifest-schema.js";
12
+ import { lookupContract, } from "../contracts/tool-contract-registry.js";
13
+ // ── Semver comparison ───────────────────────────────────────────────────
14
+ function parseSemver(version) {
15
+ const parts = version.split(".").map(Number);
16
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
17
+ }
18
+ /**
19
+ * Returns true if `actual` >= `required` (semver).
20
+ */
21
+ export function semverSatisfies(actual, required) {
22
+ const [aMajor, aMinor, aPatch] = parseSemver(actual);
23
+ const [rMajor, rMinor, rPatch] = parseSemver(required);
24
+ if (aMajor !== rMajor)
25
+ return aMajor > rMajor;
26
+ if (aMinor !== rMinor)
27
+ return aMinor > rMinor;
28
+ return aPatch >= rPatch;
29
+ }
30
+ // ── Registry ────────────────────────────────────────────────────────────
31
+ const skillMap = new Map();
32
+ /**
33
+ * Registers a validated skill manifest in the registry.
34
+ * Returns validation result; rejects invalid manifests.
35
+ */
36
+ export function registerSkill(raw) {
37
+ const result = validateManifest(raw);
38
+ if (result.valid) {
39
+ skillMap.set(result.manifest.skillId, result.manifest);
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Look up a registered skill by ID.
45
+ */
46
+ export function lookupSkill(skillId) {
47
+ const manifest = skillMap.get(skillId);
48
+ if (manifest) {
49
+ return { found: true, manifest };
50
+ }
51
+ return {
52
+ found: false,
53
+ skillId,
54
+ reason: `No skill registered with id '${skillId}'`,
55
+ };
56
+ }
57
+ /**
58
+ * Returns all registered skill manifests.
59
+ */
60
+ export function getAllSkills() {
61
+ return Array.from(skillMap.values());
62
+ }
63
+ /**
64
+ * Checks whether all required tools for a skill are available
65
+ * in the contract registry with sufficient versions.
66
+ *
67
+ * This is the compatibility gate: execution MUST be blocked
68
+ * if any required tool contract is missing or below the
69
+ * minimum version.
70
+ */
71
+ export function checkCompatibility(manifest) {
72
+ const failures = [];
73
+ const checkedTools = [];
74
+ for (const req of manifest.requiredTools) {
75
+ checkedTools.push(req.toolName);
76
+ const lookup = lookupContract(req.toolName);
77
+ if (!lookup.found) {
78
+ failures.push({
79
+ toolName: req.toolName,
80
+ requiredVersion: req.minContractVersion,
81
+ reason: `Tool contract not registered: ${req.toolName}`,
82
+ });
83
+ continue;
84
+ }
85
+ if (!semverSatisfies(lookup.contract.contractVersion, req.minContractVersion)) {
86
+ failures.push({
87
+ toolName: req.toolName,
88
+ requiredVersion: req.minContractVersion,
89
+ reason: `Contract version ${lookup.contract.contractVersion} < required ${req.minContractVersion}`,
90
+ });
91
+ }
92
+ }
93
+ if (failures.length > 0) {
94
+ return { compatible: false, skillId: manifest.skillId, failures };
95
+ }
96
+ return { compatible: true, skillId: manifest.skillId, checkedTools };
97
+ }
98
+ /**
99
+ * Checks compatibility and throws if requirements are not met.
100
+ */
101
+ export function requireCompatibility(manifest) {
102
+ const result = checkCompatibility(manifest);
103
+ if (!result.compatible) {
104
+ const details = result.failures
105
+ .map((f) => ` ${f.toolName}: ${f.reason}`)
106
+ .join("\n");
107
+ throw new Error(`Compatibility check failed for skill '${manifest.skillId}':\n${details}`);
108
+ }
109
+ return result;
110
+ }
111
+ /**
112
+ * Clears all registered skills (for testing only).
113
+ */
114
+ export function _resetSkillRegistry() {
115
+ skillMap.clear();
116
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OpenTelemetry tracing for the NestPilot MCP server.
3
+ *
4
+ * Ported from apps/mcp-edge to provide tool-level spans and observability.
5
+ * Initialized conditionally based on config.enableConsoleTracing.
6
+ */
7
+ import { type Span } from "@opentelemetry/api";
8
+ import type { AppConfig } from "./config.js";
9
+ export declare const tracer: import("@opentelemetry/api").Tracer;
10
+ export declare function initializeTelemetry(config: AppConfig): void;
11
+ export declare function shutdownTelemetry(): Promise<void>;
12
+ export declare function withToolSpan<T>(spanName: string, attributes: Record<string, string | number | boolean>, fn: (span: Span) => Promise<T>): Promise<T>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * OpenTelemetry tracing for the NestPilot MCP server.
3
+ *
4
+ * Ported from apps/mcp-edge to provide tool-level spans and observability.
5
+ * Initialized conditionally based on config.enableConsoleTracing.
6
+ */
7
+ import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
8
+ import { Resource } from "@opentelemetry/resources";
9
+ import { BatchSpanProcessor, ConsoleSpanExporter, } from "@opentelemetry/sdk-trace-base";
10
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
11
+ import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
12
+ const SERVICE_NAME = "nestpilot-mcp-app";
13
+ const SERVICE_VERSION = "1.0.0";
14
+ let provider = null;
15
+ let initialized = false;
16
+ export const tracer = trace.getTracer(SERVICE_NAME, SERVICE_VERSION);
17
+ export function initializeTelemetry(config) {
18
+ if (initialized || !config.enableConsoleTracing) {
19
+ return;
20
+ }
21
+ provider = new NodeTracerProvider({
22
+ resource: new Resource({
23
+ [SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME,
24
+ [SemanticResourceAttributes.SERVICE_VERSION]: SERVICE_VERSION,
25
+ }),
26
+ });
27
+ provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));
28
+ provider.register();
29
+ initialized = true;
30
+ process.once("beforeExit", () => {
31
+ void shutdownTelemetry();
32
+ });
33
+ }
34
+ export async function shutdownTelemetry() {
35
+ if (!provider) {
36
+ return;
37
+ }
38
+ await provider.shutdown().catch(() => { });
39
+ }
40
+ export async function withToolSpan(spanName, attributes, fn) {
41
+ return tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes }, async (span) => {
42
+ try {
43
+ const result = await fn(span);
44
+ span.setStatus({ code: SpanStatusCode.OK });
45
+ return result;
46
+ }
47
+ catch (error) {
48
+ span.recordException(error);
49
+ span.setStatus({
50
+ code: SpanStatusCode.ERROR,
51
+ message: String(error),
52
+ });
53
+ throw error;
54
+ }
55
+ finally {
56
+ span.end();
57
+ }
58
+ });
59
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared type definitions for the Medicare Guardian MCP App view.
3
+ */
4
+ export interface Deadline {
5
+ type: string;
6
+ startDate: string;
7
+ endDate: string;
8
+ status: string;
9
+ daysRemaining?: number;
10
+ }
11
+ export interface PenaltyInfo {
12
+ monthly: number;
13
+ lifetime?: number;
14
+ notes?: string;
15
+ }
16
+ export interface ChecklistItem {
17
+ id?: string;
18
+ title: string;
19
+ description: string;
20
+ }
21
+ export interface Explanation {
22
+ topic: string;
23
+ reasoning: string;
24
+ }
25
+ export interface AnalysisResult {
26
+ status: string;
27
+ healthScore: number;
28
+ deadlines: Deadline[];
29
+ penalties: {
30
+ partBPenalty?: PenaltyInfo;
31
+ partDPenalty?: PenaltyInfo;
32
+ };
33
+ checklist: ChecklistItem[];
34
+ explanations: Explanation[];
35
+ error?: boolean;
36
+ message?: string;
37
+ }
38
+ export interface FormData {
39
+ dateOfBirth: string;
40
+ employmentStatus: string;
41
+ coverageType: string;
42
+ employerSize: string | null;
43
+ hsaContributor: boolean;
44
+ hasCreditableDrugCoverage: boolean;
45
+ }
46
+ export type ViewState = "input" | "results" | "subscribe" | "sep_check";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared type definitions for the Medicare Guardian MCP App view.
3
+ */
4
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Agent Runtime MCP tools — list_skills, run_skill, run_plan.
3
+ *
4
+ * These tools expose the NestPilot Agent Runtime's skill registry
5
+ * and orchestration capabilities through MCP, enabling any MCP host
6
+ * (Claude Desktop, ChatGPT, etc.) to access the agent platform.
7
+ *
8
+ * Communicates with the agent-runtime service via HTTP.
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { type AuthContext } from "./mcp-helpers.js";
12
+ export declare function registerAgentTools(server: McpServer, _authCtx?: AuthContext): void;
@@ -0,0 +1,141 @@
1
+ import { z } from "zod";
2
+ import { toCallToolResult } from "./mcp-helpers.js";
3
+ const AGENT_RUNTIME_URL = process.env.AGENT_RUNTIME_URL ?? "http://localhost:3002";
4
+ async function agentRuntimeGet(path) {
5
+ const url = `${AGENT_RUNTIME_URL}${path}`;
6
+ try {
7
+ const res = await fetch(url, {
8
+ headers: { Accept: "application/json" },
9
+ signal: AbortSignal.timeout(10_000),
10
+ });
11
+ if (!res.ok) {
12
+ return { error: true, code: res.status, message: await res.text() };
13
+ }
14
+ return await res.json();
15
+ }
16
+ catch (e) {
17
+ return {
18
+ error: true,
19
+ message: `Agent runtime connection failed: ${String(e)}`,
20
+ };
21
+ }
22
+ }
23
+ async function agentRuntimePost(path, body) {
24
+ const url = `${AGENT_RUNTIME_URL}${path}`;
25
+ try {
26
+ const res = await fetch(url, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ Accept: "application/json",
31
+ },
32
+ body: JSON.stringify(body),
33
+ signal: AbortSignal.timeout(180_000),
34
+ });
35
+ if (!res.ok) {
36
+ return { error: true, code: res.status, message: await res.text() };
37
+ }
38
+ return await res.json();
39
+ }
40
+ catch (e) {
41
+ return {
42
+ error: true,
43
+ message: `Agent runtime connection failed: ${String(e)}`,
44
+ };
45
+ }
46
+ }
47
+ export function registerAgentTools(server, _authCtx) {
48
+ // ── 1. nestpilot_list_skills ──────────────────────────────────────────
49
+ server.tool("nestpilot_list_skills", `Lists all available NestPilot agent skills with their names and descriptions.
50
+
51
+ USE THIS TOOL WHEN THE USER:
52
+ - Asks "what can you do?" or wants to know available capabilities
53
+ - Needs to choose a skill before running it
54
+ - Wants to understand the agent's capabilities`, {}, async () => {
55
+ const result = await agentRuntimeGet("/skills");
56
+ const data = result;
57
+ if (data.error) {
58
+ return toCallToolResult(data, true);
59
+ }
60
+ return toCallToolResult(data);
61
+ });
62
+ // ── 2. nestpilot_run_skill ────────────────────────────────────────────
63
+ server.tool("nestpilot_run_skill", `Runs a NestPilot agent skill by name within a session. Creates a new session if none provided. Returns the agent's streaming response events.
64
+
65
+ USE THIS TOOL WHEN THE USER:
66
+ - Wants to run a specific skill (e.g., "retirement-readiness", "scenario-sweep")
67
+ - Wants to interact with the agent runtime through a skill-specific workflow
68
+
69
+ Call nestpilot_list_skills first to see available skills.`, {
70
+ skillName: z
71
+ .string()
72
+ .describe("Name of the skill to run (e.g., 'retirement-readiness', 'scenario-sweep')"),
73
+ message: z
74
+ .string()
75
+ .describe("User message to send to the skill (the skill will guide the conversation)"),
76
+ sessionId: z
77
+ .string()
78
+ .optional()
79
+ .describe("Existing session ID to continue a conversation. If omitted, a new session is created."),
80
+ }, async (args) => {
81
+ const { skillName, message, sessionId } = args;
82
+ let activeSessionId = sessionId;
83
+ // Create session if needed
84
+ if (!activeSessionId) {
85
+ const createResult = (await agentRuntimePost("/sessions", {
86
+ userId: "mcp-user",
87
+ }));
88
+ if (createResult.error) {
89
+ return toCallToolResult(createResult, true);
90
+ }
91
+ activeSessionId = createResult.id;
92
+ }
93
+ // Send message with skill context prefix
94
+ const content = `[skill:${skillName}] ${message}`;
95
+ const result = (await agentRuntimePost(`/sessions/${activeSessionId}/messages`, { content }));
96
+ if (result.error) {
97
+ return toCallToolResult(result, true);
98
+ }
99
+ return toCallToolResult({
100
+ sessionId: activeSessionId,
101
+ ...result,
102
+ });
103
+ });
104
+ // ── 3. nestpilot_run_plan ─────────────────────────────────────────────
105
+ server.tool("nestpilot_run_plan", `Runs a full retirement planning session through the agent runtime. This is a convenience tool that combines creating a session, running the retirement-readiness skill, and returning the results.
106
+
107
+ USE THIS TOOL WHEN THE USER:
108
+ - Wants a complete retirement readiness assessment
109
+ - Says "check my retirement plan" or "am I on track?"
110
+ - Provides age, savings, and contribution information and wants a full analysis`, {
111
+ message: z
112
+ .string()
113
+ .describe("User's retirement planning question or data (e.g., 'I'm 40, have $250k saved, contributing $2k/month')"),
114
+ sessionId: z
115
+ .string()
116
+ .optional()
117
+ .describe("Existing session ID to continue. If omitted, starts fresh."),
118
+ }, async (args) => {
119
+ const { message, sessionId } = args;
120
+ let activeSessionId = sessionId;
121
+ if (!activeSessionId) {
122
+ const createResult = (await agentRuntimePost("/sessions", {
123
+ userId: "mcp-user",
124
+ }));
125
+ if (createResult.error) {
126
+ return toCallToolResult(createResult, true);
127
+ }
128
+ activeSessionId = createResult.id;
129
+ }
130
+ const content = `[skill:retirement-readiness] ${message}`;
131
+ const result = (await agentRuntimePost(`/sessions/${activeSessionId}/messages`, { content }));
132
+ if (result.error) {
133
+ return toCallToolResult(result, true);
134
+ }
135
+ return toCallToolResult({
136
+ sessionId: activeSessionId,
137
+ skill: "retirement-readiness",
138
+ ...result,
139
+ });
140
+ });
141
+ }
@@ -0,0 +1,9 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { type AuthContext } from "./mcp-helpers.js";
4
+ export declare function usesActivePlanAlias(planId?: string): boolean;
5
+ export declare function resolveSavedPlanId(planId: string | undefined, authCtx?: AuthContext): Promise<{
6
+ planId?: string;
7
+ errorResult?: CallToolResult;
8
+ }>;
9
+ export declare function registerForecastManagementTools(server: McpServer, authCtx?: AuthContext): void;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Forecast management MCP tools — saved forecast operations.
3
+ *
4
+ * Mirrors the forecast management API used by the web app (apps/web/hooks/useApi.ts):
5
+ * run_saved_forecast, get_baseline_forecast.
6
+ */
7
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
8
+ import { z } from "zod";
9
+ import { nestpilotClient } from "../nestpilot-client.js";
10
+ import { proxyGetTool, proxyPostTool, toCallToolResult, } from "./mcp-helpers.js";
11
+ const ACTIVE_PLAN_ALIASES = new Set(["default", "active", "current"]);
12
+ function buildAuthHeaders(authCtx) {
13
+ const headers = {};
14
+ if (authCtx?.bearerToken) {
15
+ headers.Authorization = `Bearer ${authCtx.bearerToken}`;
16
+ }
17
+ if (authCtx?.userId) {
18
+ headers["X-User-ID"] = authCtx.userId;
19
+ }
20
+ return headers;
21
+ }
22
+ export function usesActivePlanAlias(planId) {
23
+ const normalized = planId?.trim().toLowerCase();
24
+ if (!normalized)
25
+ return true;
26
+ return ACTIVE_PLAN_ALIASES.has(normalized);
27
+ }
28
+ function extractPlanId(payload) {
29
+ const candidate = payload.id ?? payload.planId;
30
+ if (typeof candidate !== "string")
31
+ return undefined;
32
+ const trimmed = candidate.trim();
33
+ return trimmed.length > 0 ? trimmed : undefined;
34
+ }
35
+ export async function resolveSavedPlanId(planId, authCtx) {
36
+ if (!usesActivePlanAlias(planId)) {
37
+ return { planId: planId?.trim() };
38
+ }
39
+ const activePlanResult = await nestpilotClient.get("/api/plans/active", undefined, { headers: buildAuthHeaders(authCtx) });
40
+ if (activePlanResult.error) {
41
+ return {
42
+ errorResult: toCallToolResult({
43
+ error: true,
44
+ code: activePlanResult.code ?? 404,
45
+ message: activePlanResult.message ??
46
+ "Unable to resolve active plan for this user.",
47
+ }, true),
48
+ };
49
+ }
50
+ const activePlanId = extractPlanId(activePlanResult);
51
+ if (!activePlanId) {
52
+ return {
53
+ errorResult: toCallToolResult({
54
+ error: true,
55
+ code: 404,
56
+ message: "No active plan found. Create or activate a saved plan first.",
57
+ }, true),
58
+ };
59
+ }
60
+ return { planId: activePlanId };
61
+ }
62
+ export function registerForecastManagementTools(server, authCtx) {
63
+ // ── 1. run_saved_forecast ───────────────────────────────────────────────
64
+ registerAppTool(server, "run_saved_forecast", {
65
+ title: "Run Saved Forecast",
66
+ description: `Triggers a forecast run for a saved plan. Returns a forecastId and status.
67
+
68
+ USE THIS TOOL WHEN THE USER:
69
+ - Wants to run a forecast on one of their saved plans
70
+ - Says "run forecast on my plan" or "forecast plan X"
71
+ - Needs to compute or refresh the baseline or scenario forecast for a persisted plan
72
+
73
+ REQUIRES authentication — operates on saved plans.`,
74
+ inputSchema: {
75
+ planId: z
76
+ .string()
77
+ .describe("UUID of the saved plan. Alias values 'default', 'active', or 'current' use the active plan."),
78
+ runType: z
79
+ .enum(["baseline", "scenario"])
80
+ .describe("Type of forecast run: 'baseline' or 'scenario'"),
81
+ scenarioId: z
82
+ .string()
83
+ .optional()
84
+ .describe("UUID of the scenario to apply (required when runType is 'scenario')"),
85
+ },
86
+ _meta: { ui: { visibility: ["model", "app"] } },
87
+ }, async (args) => {
88
+ const { planId, runType, scenarioId } = args;
89
+ const resolved = await resolveSavedPlanId(planId, authCtx);
90
+ if (resolved.errorResult)
91
+ return resolved.errorResult;
92
+ if (!resolved.planId) {
93
+ return toCallToolResult({
94
+ error: true,
95
+ code: 400,
96
+ message: "Missing planId after active plan resolution.",
97
+ }, true);
98
+ }
99
+ return proxyPostTool("/api/forecasts/run", { planId: resolved.planId, runType, scenarioId }, { toolName: "run_saved_forecast", authCtx });
100
+ });
101
+ // ── 2. get_baseline_forecast ────────────────────────────────────────────
102
+ registerAppTool(server, "get_baseline_forecast", {
103
+ title: "Get Baseline Forecast",
104
+ description: `Retrieves the most recent baseline forecast result for a saved plan.
105
+ Returns the forecast JSON, derived metrics, and forecast ID.
106
+
107
+ USE THIS TOOL WHEN THE USER:
108
+ - Wants to see the latest forecast results for a saved plan
109
+ - Asks "show me my forecast" or "what's my baseline?"
110
+ - Needs forecast data to display in charts or reports
111
+
112
+ REQUIRES authentication — operates on saved plans.`,
113
+ inputSchema: {
114
+ planId: z
115
+ .string()
116
+ .describe("UUID of the saved plan. Alias values 'default', 'active', or 'current' use the active plan."),
117
+ },
118
+ _meta: { ui: { visibility: ["model", "app"] } },
119
+ }, async (args) => {
120
+ const { planId } = args;
121
+ const resolved = await resolveSavedPlanId(planId, authCtx);
122
+ if (resolved.errorResult)
123
+ return resolved.errorResult;
124
+ if (!resolved.planId) {
125
+ return toCallToolResult({
126
+ error: true,
127
+ code: 400,
128
+ message: "Missing planId after active plan resolution.",
129
+ }, true);
130
+ }
131
+ return proxyGetTool(`/api/forecasts/baseline`, { planId: resolved.planId }, { toolName: "get_baseline_forecast", authCtx });
132
+ });
133
+ }
@@ -0,0 +1,8 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { LocalPlanStore } from "../src/local/local-plan-store.js";
3
+ import type { CloudComputeClient } from "../src/local/cloud-compute-client.js";
4
+ /**
5
+ * Registers local-mode MCP tools on the server.
6
+ * These tools handle data locally and delegate compute to the cloud.
7
+ */
8
+ export declare function registerLocalPlanTools(server: McpServer, store: LocalPlanStore, computeClient: CloudComputeClient): void;