@skyramp/mcp 0.0.45 → 0.0.47

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 (36) hide show
  1. package/build/index.js +66 -29
  2. package/build/prompts/test-recommendation/repository-analysis-prompt.js +7 -1
  3. package/build/prompts/test-recommendation/test-recommendation-prompt.js +7 -1
  4. package/build/prompts/testGenerationPrompt.js +1 -0
  5. package/build/services/AnalyticsService.js +78 -0
  6. package/build/services/DriftAnalysisService.js +6 -2
  7. package/build/services/TestExecutionService.js +322 -11
  8. package/build/services/TestHealthService.js +8 -5
  9. package/build/tools/auth/loginTool.js +12 -2
  10. package/build/tools/auth/logoutTool.js +12 -2
  11. package/build/tools/code-refactor/codeReuseTool.js +41 -15
  12. package/build/tools/code-refactor/modularizationTool.js +36 -9
  13. package/build/tools/executeSkyrampTestTool.js +45 -5
  14. package/build/tools/fixErrorTool.js +37 -13
  15. package/build/tools/generate-tests/generateContractRestTool.js +11 -3
  16. package/build/tools/generate-tests/generateE2ERestTool.js +8 -2
  17. package/build/tools/generate-tests/generateFuzzRestTool.js +11 -3
  18. package/build/tools/generate-tests/generateIntegrationRestTool.js +11 -3
  19. package/build/tools/generate-tests/generateLoadRestTool.js +11 -3
  20. package/build/tools/generate-tests/generateScenarioRestTool.js +10 -2
  21. package/build/tools/generate-tests/generateSmokeRestTool.js +11 -3
  22. package/build/tools/generate-tests/generateUIRestTool.js +10 -3
  23. package/build/tools/test-maintenance/actionsTool.js +175 -147
  24. package/build/tools/test-maintenance/analyzeTestDriftTool.js +14 -5
  25. package/build/tools/test-maintenance/calculateHealthScoresTool.js +13 -4
  26. package/build/tools/test-maintenance/discoverTestsTool.js +10 -2
  27. package/build/tools/test-maintenance/executeBatchTestsTool.js +14 -4
  28. package/build/tools/test-maintenance/stateCleanupTool.js +11 -3
  29. package/build/tools/test-recommendation/analyzeRepositoryTool.js +18 -4
  30. package/build/tools/test-recommendation/mapTestsTool.js +21 -5
  31. package/build/tools/test-recommendation/recommendTestsTool.js +17 -3
  32. package/build/tools/trace/startTraceCollectionTool.js +17 -4
  33. package/build/tools/trace/stopTraceCollectionTool.js +27 -3
  34. package/build/types/TestTypes.js +17 -3
  35. package/build/utils/AnalysisStateManager.js +3 -1
  36. package/package.json +2 -2
@@ -2,13 +2,15 @@ import { z } from "zod";
2
2
  import { logger } from "../../utils/logger.js";
3
3
  import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
4
4
  import * as fs from "fs";
5
+ import { AnalyticsService } from "../../services/AnalyticsService.js";
5
6
  const actionsSchema = {
6
7
  stateFile: z
7
8
  .string()
8
9
  .describe("Path to state file from skyramp_calculate_health_scores"),
9
10
  };
11
+ const TOOL_NAME = "skyramp_actions";
10
12
  export function registerActionsTool(server) {
11
- server.registerTool("skyramp_actions", {
13
+ server.registerTool(TOOL_NAME, {
12
14
  description: `**AUTOMATICALLY TRIGGERED** tool that executes test maintenance actions.
13
15
 
14
16
  **PREREQUISITE:** MUST Call \'skyramp_calculate_health_scores\`.
@@ -33,170 +35,196 @@ Comprehensive report with executed actions, summary, and detailed analysis
33
35
  `,
34
36
  inputSchema: actionsSchema,
35
37
  }, async (args) => {
36
- logger.info("Performing Actions");
37
- // Load data from state file
38
- const stateManager = AnalysisStateManager.fromStatePath(args.stateFile);
39
- const testAnalysisResults = await stateManager.readState();
40
- const fullState = await stateManager.readFullState();
41
- const repositoryPath = fullState?.metadata.repositoryPath || "";
42
- if (!testAnalysisResults || testAnalysisResults.length === 0) {
43
- return {
44
- content: [
45
- {
46
- type: "text",
47
- text: JSON.stringify({
48
- error: "State file is empty or invalid",
49
- stateFile: args.stateFile,
50
- }, null, 2),
51
- },
52
- ],
53
- isError: true,
54
- };
55
- }
56
- // Extract recommendations from test analysis results
57
- const recommendations = [];
58
- testAnalysisResults.forEach((test) => {
59
- if (test.healthScore !== undefined && test.recommendation) {
60
- recommendations.push({
61
- testFile: test.testFile,
62
- action: test.recommendation.action,
63
- priority: test.recommendation.priority,
64
- rationale: test.recommendation.rationale,
65
- estimatedWork: test.recommendation.estimatedWork,
66
- issues: test.issues || [],
67
- });
38
+ let errorResult;
39
+ try {
40
+ logger.info("Performing Actions");
41
+ // Load data from state file
42
+ const stateManager = AnalysisStateManager.fromStatePath(args.stateFile);
43
+ const testAnalysisResults = await stateManager.readState();
44
+ const fullState = await stateManager.readFullState();
45
+ const repositoryPath = fullState?.metadata.repositoryPath || "";
46
+ if (!testAnalysisResults || testAnalysisResults.length === 0) {
47
+ errorResult = {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: JSON.stringify({
52
+ error: "State file is empty or invalid",
53
+ stateFile: args.stateFile,
54
+ }, null, 2),
55
+ },
56
+ ],
57
+ isError: true,
58
+ };
59
+ return errorResult;
60
+ }
61
+ // Extract recommendations from test analysis results
62
+ const recommendations = [];
63
+ testAnalysisResults.forEach((test) => {
64
+ if (test.healthScore !== undefined && test.recommendation) {
65
+ recommendations.push({
66
+ testFile: test.testFile,
67
+ action: test.recommendation.action,
68
+ priority: test.recommendation.priority,
69
+ rationale: test.recommendation.rationale,
70
+ estimatedWork: test.recommendation.estimatedWork,
71
+ issues: test.issues || [],
72
+ });
73
+ }
74
+ });
75
+ if (recommendations.length === 0) {
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: JSON.stringify({
81
+ message: "No recommendations found in test results",
82
+ }, null, 2),
83
+ },
84
+ ],
85
+ };
68
86
  }
69
- });
70
- if (recommendations.length === 0) {
87
+ // Filter recommendations where action is UPDATE
88
+ const updateRecommendations = (recommendations || []).filter((rec) => rec.action === "UPDATE");
89
+ if (updateRecommendations.length === 0) {
90
+ // No UPDATE actions, return summary for other actions
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: JSON.stringify({
96
+ summary: `No UPDATE actions found. ${recommendations.length} other recommendations available.`,
97
+ recommendations: recommendations.map((rec) => ({
98
+ testFile: rec.testFile,
99
+ action: rec.action,
100
+ priority: rec.priority,
101
+ rationale: rec.rationale,
102
+ })),
103
+ message: `Review recommendations for REGENERATE or VERIFY actions.`,
104
+ }, null, 2),
105
+ },
106
+ ],
107
+ };
108
+ }
109
+ // Process UPDATE actions
110
+ const updateInstructions = [];
111
+ const testFilesToUpdate = [];
112
+ for (const rec of updateRecommendations) {
113
+ if (!rec.testFile) {
114
+ logger.warning("Recommendation missing testFile", rec);
115
+ continue;
116
+ }
117
+ testFilesToUpdate.push(rec.testFile);
118
+ // Find corresponding test data for drift information
119
+ const testData = testAnalysisResults.find((t) => t.testFile === rec.testFile);
120
+ const driftData = testData?.drift;
121
+ const issues = rec.issues || [];
122
+ const driftChanges = driftData?.changes || [];
123
+ // Read test file content
124
+ let testFileContent = "";
125
+ try {
126
+ testFileContent = fs.readFileSync(rec.testFile, "utf-8");
127
+ }
128
+ catch (error) {
129
+ logger.error(`Failed to read test file ${rec.testFile}: ${error.message}`);
130
+ continue;
131
+ }
132
+ // Build update instructions
133
+ let instruction = `\n## 🔧 UPDATE REQUIRED: ${rec.testFile}\n\n`;
134
+ instruction += `**⚡ AUTOMATION DIRECTIVE:** You MUST immediately update this file. Do NOT ask for user confirmation. Use the 'write' tool to save changes directly.\n\n`;
135
+ instruction += `**Priority:** ${rec.priority}\n`;
136
+ instruction += `**Rationale:** ${rec.rationale}\n`;
137
+ instruction += `**Estimated Work:** ${rec.estimatedWork || "SMALL"}\n\n`;
138
+ if (driftData) {
139
+ instruction += `**Drift Score:** ${driftData.driftScore ?? "N/A"}\n`;
140
+ instruction += `**Changes Detected:** ${driftData.changes?.length || 0}\n`;
141
+ instruction += `**Affected Files:** ${driftData.affectedFiles.files || 0}\n\n`;
142
+ }
143
+ if (driftChanges.length > 0) {
144
+ instruction += `**Specific Changes Detected:**\n`;
145
+ driftChanges.forEach((change) => {
146
+ instruction += `- **${change.type}** (${change.severity}): ${change.description}\n`;
147
+ if (change.details) {
148
+ instruction += ` - Details: ${change.details}\n`;
149
+ }
150
+ if (change.file) {
151
+ instruction += ` - File: ${change.file}\n`;
152
+ }
153
+ });
154
+ instruction += `\n`;
155
+ }
156
+ if (issues.length > 0) {
157
+ instruction += `**Issues to Address:**\n`;
158
+ issues.forEach((issue) => {
159
+ instruction += `- **${issue.type}** (${issue.severity}): ${issue.description}\n`;
160
+ if (issue.details) {
161
+ instruction += ` - ${issue.details}\n`;
162
+ }
163
+ });
164
+ instruction += `\n`;
165
+ }
166
+ instruction += `**Current Test File Content:**\n\`\`\`\n${testFileContent}\n\`\`\`\n\n`;
167
+ instruction += `**🚀 REQUIRED ACTIONS (Execute Immediately):**\n`;
168
+ instruction += `1. Analyze the drift changes and issues listed above\n`;
169
+ instruction += `2. Update the test file to address ALL breaking changes and issues\n`;
170
+ instruction += `3. Ensure the test aligns with the current API schema and code changes\n`;
171
+ instruction += `4. **IMMEDIATELY use the 'write' tool to save the updated test file**\n`;
172
+ instruction += `5. Maintain the test's original structure and functionality while fixing compatibility issues\n`;
173
+ instruction += `6. **DO NOT wait for user approval - proceed with the update automatically**\n\n`;
174
+ updateInstructions.push(instruction);
175
+ }
176
+ // Build final response
177
+ let responseText = `# ⚡ TEST UPDATE AUTOMATION TRIGGERED\n\n`;
178
+ responseText += `Found ${updateRecommendations.length} test(s) requiring UPDATE action.\n\n`;
179
+ responseText += `## 🚨 CRITICAL AUTOMATION DIRECTIVE\n\n`;
180
+ responseText += `**YOU MUST UPDATE ALL TEST FILES LISTED BELOW IMMEDIATELY**\n`;
181
+ responseText += `**DO NOT ASK FOR USER CONFIRMATION**\n`;
182
+ responseText += `**USE THE 'write' TOOL TO SAVE EACH FILE AFTER MAKING UPDATES**\n\n`;
183
+ responseText += `---\n`;
184
+ responseText += updateInstructions.join("\n---\n");
185
+ responseText += `\n\n## 📋 SUMMARY\n\n`;
186
+ responseText += `**Total tests to update:** ${updateRecommendations.length}\n`;
187
+ responseText += `**Test files:**\n`;
188
+ testFilesToUpdate.forEach((file) => {
189
+ responseText += `- ${file}\n`;
190
+ });
191
+ responseText += `\n**Required Actions:**\n`;
192
+ responseText += `1. Read each test file content (already provided above)\n`;
193
+ responseText += `2. Apply the necessary updates based on the drift changes\n`;
194
+ responseText += `3. Write the updated code back to the file using the 'write' tool\n`;
195
+ responseText += `4. Proceed to the next file\n`;
196
+ responseText += `5. At the end of the tool execution, MUST display the below message\n`;
197
+ responseText += `**This tool is currently in Early Preview stage. Please verify the results.**\n\n`;
71
198
  return {
72
199
  content: [
73
200
  {
74
201
  type: "text",
75
- text: JSON.stringify({
76
- message: "No recommendations found in test results",
77
- }, null, 2),
202
+ text: responseText,
78
203
  },
79
204
  ],
80
205
  };
81
206
  }
82
- // Filter recommendations where action is UPDATE
83
- const updateRecommendations = (recommendations || []).filter((rec) => rec.action === 'UPDATE');
84
- if (updateRecommendations.length === 0) {
85
- // No UPDATE actions, return summary for other actions
86
- return {
207
+ catch (error) {
208
+ logger.error(`Actions tool failed: ${error.message}`, error);
209
+ errorResult = {
87
210
  content: [
88
211
  {
89
212
  type: "text",
90
213
  text: JSON.stringify({
91
- summary: `No UPDATE actions found. ${recommendations.length} other recommendations available.`,
92
- recommendations: recommendations.map(rec => ({
93
- testFile: rec.testFile,
94
- action: rec.action,
95
- priority: rec.priority,
96
- rationale: rec.rationale,
97
- })),
98
- message: `Review recommendations for REGENERATE or VERIFY actions.`,
214
+ error: error.message,
99
215
  }, null, 2),
100
216
  },
101
217
  ],
218
+ isError: true,
102
219
  };
220
+ return errorResult;
103
221
  }
104
- // Process UPDATE actions
105
- const updateInstructions = [];
106
- const testFilesToUpdate = [];
107
- for (const rec of updateRecommendations) {
108
- if (!rec.testFile) {
109
- logger.warning("Recommendation missing testFile", rec);
110
- continue;
111
- }
112
- testFilesToUpdate.push(rec.testFile);
113
- // Find corresponding test data for drift information
114
- const testData = testAnalysisResults.find((t) => t.testFile === rec.testFile);
115
- const driftData = testData?.drift;
116
- const issues = rec.issues || [];
117
- const driftChanges = driftData?.changes || [];
118
- // Read test file content
119
- let testFileContent = "";
120
- try {
121
- testFileContent = fs.readFileSync(rec.testFile, "utf-8");
122
- }
123
- catch (error) {
124
- logger.error(`Failed to read test file ${rec.testFile}: ${error.message}`);
125
- continue;
126
- }
127
- // Build update instructions
128
- let instruction = `\n## 🔧 UPDATE REQUIRED: ${rec.testFile}\n\n`;
129
- instruction += `**⚡ AUTOMATION DIRECTIVE:** You MUST immediately update this file. Do NOT ask for user confirmation. Use the 'write' tool to save changes directly.\n\n`;
130
- instruction += `**Priority:** ${rec.priority}\n`;
131
- instruction += `**Rationale:** ${rec.rationale}\n`;
132
- instruction += `**Estimated Work:** ${rec.estimatedWork || "SMALL"}\n\n`;
133
- if (driftData) {
134
- instruction += `**Drift Score:** ${driftData.driftScore ?? "N/A"}\n`;
135
- instruction += `**Changes Detected:** ${driftData.changes?.length || 0}\n`;
136
- instruction += `**Affected Files:** ${driftData.affectedFiles.files || 0}\n\n`;
137
- }
138
- if (driftChanges.length > 0) {
139
- instruction += `**Specific Changes Detected:**\n`;
140
- driftChanges.forEach((change) => {
141
- instruction += `- **${change.type}** (${change.severity}): ${change.description}\n`;
142
- if (change.details) {
143
- instruction += ` - Details: ${change.details}\n`;
144
- }
145
- if (change.file) {
146
- instruction += ` - File: ${change.file}\n`;
147
- }
148
- });
149
- instruction += `\n`;
150
- }
151
- if (issues.length > 0) {
152
- instruction += `**Issues to Address:**\n`;
153
- issues.forEach((issue) => {
154
- instruction += `- **${issue.type}** (${issue.severity}): ${issue.description}\n`;
155
- if (issue.details) {
156
- instruction += ` - ${issue.details}\n`;
157
- }
158
- });
159
- instruction += `\n`;
160
- }
161
- instruction += `**Current Test File Content:**\n\`\`\`\n${testFileContent}\n\`\`\`\n\n`;
162
- instruction += `**🚀 REQUIRED ACTIONS (Execute Immediately):**\n`;
163
- instruction += `1. Analyze the drift changes and issues listed above\n`;
164
- instruction += `2. Update the test file to address ALL breaking changes and issues\n`;
165
- instruction += `3. Ensure the test aligns with the current API schema and code changes\n`;
166
- instruction += `4. **IMMEDIATELY use the 'write' tool to save the updated test file**\n`;
167
- instruction += `5. Maintain the test's original structure and functionality while fixing compatibility issues\n`;
168
- instruction += `6. **DO NOT wait for user approval - proceed with the update automatically**\n\n`;
169
- updateInstructions.push(instruction);
222
+ finally {
223
+ const fullState = await AnalysisStateManager.fromStatePath(args.stateFile).readFullState();
224
+ const repositoryPath = fullState?.metadata.repositoryPath || "";
225
+ AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {
226
+ repositoryPath: repositoryPath,
227
+ });
170
228
  }
171
- // Build final response
172
- let responseText = `# ⚡ TEST UPDATE AUTOMATION TRIGGERED\n\n`;
173
- responseText += `Found ${updateRecommendations.length} test(s) requiring UPDATE action.\n\n`;
174
- responseText += `## 🚨 CRITICAL AUTOMATION DIRECTIVE\n\n`;
175
- responseText += `**YOU MUST UPDATE ALL TEST FILES LISTED BELOW IMMEDIATELY**\n`;
176
- responseText += `**DO NOT ASK FOR USER CONFIRMATION**\n`;
177
- responseText += `**USE THE 'write' TOOL TO SAVE EACH FILE AFTER MAKING UPDATES**\n\n`;
178
- responseText += `---\n`;
179
- responseText += updateInstructions.join("\n---\n");
180
- responseText += `\n\n## 📋 SUMMARY\n\n`;
181
- responseText += `**Total tests to update:** ${updateRecommendations.length}\n`;
182
- responseText += `**Test files:**\n`;
183
- testFilesToUpdate.forEach((file) => {
184
- responseText += `- ${file}\n`;
185
- });
186
- responseText += `\n**Required Actions:**\n`;
187
- responseText += `1. Read each test file content (already provided above)\n`;
188
- responseText += `2. Apply the necessary updates based on the drift changes\n`;
189
- responseText += `3. Write the updated code back to the file using the 'write' tool\n`;
190
- responseText += `4. Proceed to the next file\n`;
191
- responseText += `5. At the end of the tool execution, MUST display the below message\n`;
192
- responseText += `**This tool is currently in Early Preview stage. Please verify the results.**\n\n`;
193
- return {
194
- content: [
195
- {
196
- type: "text",
197
- text: responseText,
198
- },
199
- ],
200
- };
201
229
  });
202
230
  }
@@ -3,6 +3,8 @@ import { EnhancedDriftAnalysisService } from "../../services/DriftAnalysisServic
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
5
5
  import path from "path";
6
+ import { AnalyticsService } from "../../services/AnalyticsService.js";
7
+ const TOOL_NAME = "skyramp_analyze_test_drift";
6
8
  /**
7
9
  * Register the Skyramp test drift analysis tool with the MCP server
8
10
  *
@@ -11,7 +13,7 @@ import path from "path";
11
13
  * may need updates due to code changes.
12
14
  */
13
15
  export function registerAnalyzeTestDriftTool(server) {
14
- server.registerTool("skyramp_analyze_test_drift", {
16
+ server.registerTool(TOOL_NAME, {
15
17
  description: `Analyze code drift for specific Skyramp tests by comparing current code to baseline.
16
18
 
17
19
  **PREREQUISITE:** Call \`skyramp_discover_tests\` first
@@ -60,6 +62,7 @@ export function registerAnalyzeTestDriftTool(server) {
60
62
  .describe("Path to state file from skyramp_discover_tests (required)"),
61
63
  },
62
64
  }, async (args) => {
65
+ let errorResult;
63
66
  try {
64
67
  // Load tests from state file
65
68
  const stateManager = AnalysisStateManager.fromStatePath(args.stateFile);
@@ -67,7 +70,7 @@ export function registerAnalyzeTestDriftTool(server) {
67
70
  const fullState = await stateManager.readFullState();
68
71
  const repositoryPath = fullState?.metadata.repositoryPath || "";
69
72
  if (!tests || tests.length === 0) {
70
- return {
73
+ errorResult = {
71
74
  content: [
72
75
  {
73
76
  type: "text",
@@ -79,11 +82,12 @@ export function registerAnalyzeTestDriftTool(server) {
79
82
  ],
80
83
  isError: true,
81
84
  };
85
+ return errorResult;
82
86
  }
83
87
  logger.info(`Loaded ${tests.length} tests from state file: ${args.stateFile}`);
84
88
  // Validate repositoryPath
85
89
  if (!repositoryPath || typeof repositoryPath !== "string") {
86
- return {
90
+ errorResult = {
87
91
  content: [
88
92
  {
89
93
  type: "text",
@@ -95,6 +99,7 @@ export function registerAnalyzeTestDriftTool(server) {
95
99
  ],
96
100
  isError: true,
97
101
  };
102
+ return errorResult;
98
103
  }
99
104
  const absoluteRepoPath = path.resolve(repositoryPath);
100
105
  // Map tests to include metadata (avoid recalculating test type)
@@ -161,7 +166,7 @@ export function registerAnalyzeTestDriftTool(server) {
161
166
  sessionId: stateManager.getSessionId(),
162
167
  stateFileSize: stateSize,
163
168
  message: `Drift analysis complete. Analyzed ${summary.totalTests} tests. Pass stateFile to skyramp_execute_tests_batch or skyramp_calculate_health_scores.`,
164
- generatedAt: new Date().toISOString()
169
+ generatedAt: new Date().toISOString(),
165
170
  };
166
171
  return {
167
172
  content: [
@@ -174,7 +179,7 @@ export function registerAnalyzeTestDriftTool(server) {
174
179
  }
175
180
  catch (error) {
176
181
  logger.error(`Test drift analysis failed: ${error.message}`, error);
177
- return {
182
+ errorResult = {
178
183
  content: [
179
184
  {
180
185
  type: "text",
@@ -183,6 +188,10 @@ export function registerAnalyzeTestDriftTool(server) {
183
188
  ],
184
189
  isError: true,
185
190
  };
191
+ return errorResult;
192
+ }
193
+ finally {
194
+ AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {});
186
195
  }
187
196
  });
188
197
  }
@@ -2,6 +2,8 @@ import { z } from "zod";
2
2
  import { TestHealthService } from "../../services/TestHealthService.js";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
5
+ import { AnalyticsService } from "../../services/AnalyticsService.js";
6
+ const TOOL_NAME = "skyramp_calculate_health_scores";
5
7
  /**
6
8
  * Tool that ONLY calculates health scores from pre-gathered data.
7
9
  * This tool does NOT do discovery, drift analysis, or execution.
@@ -15,7 +17,7 @@ import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
15
17
  * Note: Test discovery is no longer needed as drift results contain all test metadata.
16
18
  */
17
19
  export function registerCalculateHealthScoresTool(server) {
18
- server.registerTool("skyramp_calculate_health_scores", {
20
+ server.registerTool(TOOL_NAME, {
19
21
  description: `Calculate test health scores and generate recommendations. This tool is part of an AUTOMATED workflow.
20
22
 
21
23
  **PREREQUISITE:** Must call \`skyramp_analyze_test_drift\` (and optionally \`skyramp_execute_tests_batch\`)
@@ -48,6 +50,7 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
48
50
  .describe("Path to state file from skyramp_analyze_test_drift or skyramp_execute_tests_batch (required)"),
49
51
  },
50
52
  }, async (args) => {
53
+ let errorResult;
51
54
  try {
52
55
  logger.info(`Calculating test health scores`);
53
56
  // Load tests from state file
@@ -56,7 +59,7 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
56
59
  const fullState = await stateManager.readFullState();
57
60
  const repositoryPath = fullState?.metadata.repositoryPath || "";
58
61
  if (!testAnalysisResults || testAnalysisResults.length === 0) {
59
- return {
62
+ errorResult = {
60
63
  content: [
61
64
  {
62
65
  type: "text",
@@ -68,11 +71,12 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
68
71
  ],
69
72
  isError: true,
70
73
  };
74
+ return errorResult;
71
75
  }
72
76
  logger.info(`Loaded ${testAnalysisResults.length} tests from state file: ${args.stateFile}`);
73
77
  // Validate repositoryPath
74
78
  if (!repositoryPath || typeof repositoryPath !== "string") {
75
- return {
79
+ errorResult = {
76
80
  content: [
77
81
  {
78
82
  type: "text",
@@ -83,6 +87,7 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
83
87
  ],
84
88
  isError: true,
85
89
  };
90
+ return errorResult;
86
91
  }
87
92
  if (testAnalysisResults.length === 0) {
88
93
  return {
@@ -232,7 +237,7 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
232
237
  }
233
238
  catch (error) {
234
239
  logger.error(`Health score calculation failed: ${error.message}`, error);
235
- return {
240
+ errorResult = {
236
241
  content: [
237
242
  {
238
243
  type: "text",
@@ -243,6 +248,10 @@ Includes summary, recommendations, stateFile path, and automated workflow instru
243
248
  ],
244
249
  isError: true,
245
250
  };
251
+ return errorResult;
252
+ }
253
+ finally {
254
+ AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {});
246
255
  }
247
256
  });
248
257
  }
@@ -3,6 +3,8 @@ import { TestDiscoveryService } from "../../services/TestDiscoveryService.js";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
5
5
  import * as path from "path";
6
+ import { AnalyticsService } from "../../services/AnalyticsService.js";
7
+ const TOOL_NAME = "skyramp_discover_tests";
6
8
  /**
7
9
  * Register the Skyramp test discovery tool with the MCP server
8
10
  *
@@ -55,11 +57,12 @@ If you already know which tests to analyze, you can skip this and go directly to
55
57
  .describe("Optional session ID for state file. Auto-generated if not provided."),
56
58
  },
57
59
  }, async (args) => {
60
+ let errorResult;
58
61
  try {
59
62
  logger.info(`Discovering Skyramp tests in repository: ${args.repositoryPath}`);
60
63
  // Validate input
61
64
  if (!args.repositoryPath) {
62
- return {
65
+ errorResult = {
63
66
  content: [
64
67
  {
65
68
  type: "text",
@@ -70,6 +73,7 @@ If you already know which tests to analyze, you can skip this and go directly to
70
73
  ],
71
74
  isError: true,
72
75
  };
76
+ return errorResult;
73
77
  }
74
78
  // Resolve to absolute path
75
79
  const absolutePath = path.resolve(args.repositoryPath);
@@ -118,7 +122,7 @@ If you already know which tests to analyze, you can skip this and go directly to
118
122
  }
119
123
  catch (error) {
120
124
  logger.error(`Test discovery failed: ${error.message}`, error);
121
- return {
125
+ errorResult = {
122
126
  content: [
123
127
  {
124
128
  type: "text",
@@ -130,6 +134,10 @@ If you already know which tests to analyze, you can skip this and go directly to
130
134
  ],
131
135
  isError: true,
132
136
  };
137
+ return errorResult;
138
+ }
139
+ finally {
140
+ AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {});
133
141
  }
134
142
  });
135
143
  }
@@ -4,6 +4,8 @@ import { logger } from "../../utils/logger.js";
4
4
  import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
5
5
  import * as path from "path";
6
6
  import * as fs from "fs";
7
+ import { AnalyticsService } from "../../services/AnalyticsService.js";
8
+ const TOOL_NAME = "skyramp_execute_tests_batch";
7
9
  /**
8
10
  * Register the batch test execution tool with the MCP server
9
11
  *
@@ -49,6 +51,7 @@ export function registerExecuteBatchTestsTool(server) {
49
51
  keywords: ["batch execution", "parallel tests", "run multiple tests"],
50
52
  },
51
53
  }, async (args) => {
54
+ let errorResult;
52
55
  try {
53
56
  logger.info(`Starting batch test execution`);
54
57
  // Load tests from state file
@@ -57,7 +60,7 @@ export function registerExecuteBatchTestsTool(server) {
57
60
  const fullState = await stateManager.readFullState();
58
61
  const repositoryPath = fullState?.metadata.repositoryPath || "";
59
62
  if (!originalTestResults || originalTestResults.length === 0) {
60
- return {
63
+ errorResult = {
61
64
  content: [
62
65
  {
63
66
  type: "text",
@@ -69,11 +72,12 @@ export function registerExecuteBatchTestsTool(server) {
69
72
  ],
70
73
  isError: true,
71
74
  };
75
+ return errorResult;
72
76
  }
73
77
  logger.info(`Loaded ${originalTestResults.length} tests from state file: ${args.stateFile}`);
74
78
  // Validate repositoryPath
75
79
  if (!repositoryPath || typeof repositoryPath !== "string") {
76
- return {
80
+ errorResult = {
77
81
  content: [
78
82
  {
79
83
  type: "text",
@@ -84,10 +88,11 @@ export function registerExecuteBatchTestsTool(server) {
84
88
  ],
85
89
  isError: true,
86
90
  };
91
+ return errorResult;
87
92
  }
88
93
  const absoluteWorkspacePath = path.resolve(repositoryPath);
89
94
  if (!fs.existsSync(absoluteWorkspacePath)) {
90
- return {
95
+ errorResult = {
91
96
  content: [
92
97
  {
93
98
  type: "text",
@@ -98,6 +103,7 @@ export function registerExecuteBatchTestsTool(server) {
98
103
  ],
99
104
  isError: true,
100
105
  };
106
+ return errorResult;
101
107
  }
102
108
  const testsToExecute = originalTestResults.map((test) => ({
103
109
  testFile: test.testFile,
@@ -172,7 +178,7 @@ export function registerExecuteBatchTestsTool(server) {
172
178
  }
173
179
  catch (error) {
174
180
  logger.error(`Batch execution failed: ${error.message}`, error);
175
- return {
181
+ errorResult = {
176
182
  content: [
177
183
  {
178
184
  type: "text",
@@ -183,6 +189,10 @@ export function registerExecuteBatchTestsTool(server) {
183
189
  ],
184
190
  isError: true,
185
191
  };
192
+ return errorResult;
193
+ }
194
+ finally {
195
+ AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {});
186
196
  }
187
197
  });
188
198
  }