@skyramp/mcp 0.0.49 → 0.0.51

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 (25) hide show
  1. package/build/prompts/startTraceCollectionPrompts.js +4 -1
  2. package/build/prompts/testGenerationPrompt.js +4 -1
  3. package/build/prompts/testHealthPrompt.js +4 -1
  4. package/build/services/DriftAnalysisService.js +31 -10
  5. package/build/services/TestExecutionService.js +39 -1
  6. package/build/services/TestGenerationService.js +1 -1
  7. package/build/tools/executeSkyrampTestTool.js +5 -0
  8. package/build/tools/generate-tests/generateE2ERestTool.js +4 -1
  9. package/build/tools/generate-tests/generateIntegrationRestTool.js +5 -2
  10. package/build/tools/generate-tests/generateLoadRestTool.js +4 -1
  11. package/build/tools/generate-tests/generateScenarioRestTool.js +8 -1
  12. package/build/tools/test-maintenance/actionsTool.js +56 -45
  13. package/build/tools/test-maintenance/analyzeTestDriftTool.js +5 -4
  14. package/build/tools/test-maintenance/calculateHealthScoresTool.js +12 -33
  15. package/build/tools/test-maintenance/discoverTestsTool.js +3 -3
  16. package/build/tools/test-maintenance/executeBatchTestsTool.js +5 -4
  17. package/build/tools/test-maintenance/stateCleanupTool.js +13 -8
  18. package/build/tools/test-recommendation/analyzeRepositoryTool.js +5 -1
  19. package/build/tools/test-recommendation/mapTestsTool.js +29 -19
  20. package/build/tools/test-recommendation/recommendTestsTool.js +27 -30
  21. package/build/tools/trace/startTraceCollectionTool.js +73 -18
  22. package/build/tools/trace/stopTraceCollectionTool.js +1 -1
  23. package/build/types/TestTypes.js +7 -1
  24. package/build/utils/AnalysisStateManager.js +73 -62
  25. package/package.json +7 -2
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
2
+ import { StateManager } from "../../utils/AnalysisStateManager.js";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { AnalyticsService } from "../../services/AnalyticsService.js";
5
5
  const TOOL_NAME = "skyramp_state_cleanup";
@@ -11,13 +11,17 @@ const TOOL_NAME = "skyramp_state_cleanup";
11
11
  */
12
12
  export function registerStateCleanupTool(server) {
13
13
  server.registerTool(TOOL_NAME, {
14
- description: `Manage and cleanup test analysis state files.
14
+ description: `Manage and cleanup Skyramp state files.
15
15
 
16
16
  **WHAT IT DOES:**
17
- - List all existing state files with details (size, age, session ID)
17
+ - List all existing state files with details (size, age, session ID, type)
18
18
  - Delete state files older than specified age
19
19
  - Show disk space usage by state files
20
20
 
21
+ **STATE TYPES MANAGED:**
22
+ - analysis: Test maintenance workflow (discovery, drift, execution, health)
23
+ - recommendation: Test recommendation workflow (analyze repo, map tests, recommend)
24
+
21
25
  **WHEN TO USE:**
22
26
  - Periodically to clean up old analysis sessions
23
27
  - To check current state files in the system
@@ -45,8 +49,8 @@ Information about state files and cleanup results.`,
45
49
  try {
46
50
  logger.info(`State file ${args.action} requested`);
47
51
  if (args.action === "list") {
48
- // List all state files
49
- const stateFiles = await AnalysisStateManager.listStateFiles();
52
+ // List all state files (analysis, recommendation)
53
+ const stateFiles = await StateManager.listStateFiles();
50
54
  if (stateFiles.length === 0) {
51
55
  return {
52
56
  content: [
@@ -72,6 +76,7 @@ Information about state files and cleanup results.`,
72
76
  };
73
77
  const fileDetails = stateFiles.map((file) => ({
74
78
  sessionId: file.sessionId,
79
+ stateType: file.stateType,
75
80
  path: file.path,
76
81
  size: formatSize(file.size),
77
82
  sizeBytes: file.size,
@@ -96,12 +101,12 @@ Information about state files and cleanup results.`,
96
101
  };
97
102
  }
98
103
  else if (args.action === "cleanup") {
99
- // Cleanup old state files
104
+ // Cleanup old state files (all types: analysis, recommendation)
100
105
  const maxAgeHours = args.maxAgeHours || 24;
101
- const deletedCount = await AnalysisStateManager.cleanupOldStateFiles(maxAgeHours);
106
+ const deletedCount = await StateManager.cleanupOldStateFiles(maxAgeHours);
102
107
  logger.info(`Cleaned up ${deletedCount} state files older than ${maxAgeHours} hours`);
103
108
  // Get remaining files
104
- const remainingFiles = await AnalysisStateManager.listStateFiles();
109
+ const remainingFiles = await StateManager.listStateFiles();
105
110
  return {
106
111
  content: [
107
112
  {
@@ -96,7 +96,11 @@ Use the following tools to gather information:
96
96
 
97
97
  ${analysisPrompt}
98
98
 
99
- After gathering all information, return a JSON object matching the RepositoryAnalysis structure shown in the prompt above.`,
99
+ After gathering all information:
100
+ 1. Return the RepositoryAnalysis JSON in your response
101
+ 2. Then call \`skyramp_map_tests\` with:
102
+ - repositoryPath: \`${params.repositoryPath}\`
103
+ - analysisReport: <the JSON you just created>`,
100
104
  },
101
105
  ],
102
106
  isError: false,
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { repositoryAnalysisSchema } from "../../types/RepositoryAnalysis.js";
3
3
  import { TestType } from "../../types/TestTypes.js";
4
4
  import { ScoringEngine } from "../../utils/scoring-engine.js";
5
+ import { StateManager, } from "../../utils/AnalysisStateManager.js";
5
6
  import { logger } from "../../utils/logger.js";
6
7
  import { AnalyticsService } from "../../services/AnalyticsService.js";
7
8
  /**
@@ -9,6 +10,9 @@ import { AnalyticsService } from "../../services/AnalyticsService.js";
9
10
  * MCP tool for calculating test priority scores
10
11
  */
11
12
  const mapTestsSchema = z.object({
13
+ repositoryPath: z
14
+ .string()
15
+ .describe("Absolute path to the repository that was analyzed (used for saving results)"),
12
16
  analysisReport: z
13
17
  .union([z.string(), repositoryAnalysisSchema])
14
18
  .describe("Repository analysis result (JSON string or object from skyramp_analyze_repository)"),
@@ -38,9 +42,10 @@ The scoring algorithm considers:
38
42
  - Available artifacts (OpenAPI specs, Playwright recordings)
39
43
  - Security requirements (authentication, sensitive data)
40
44
 
41
- Example usage (direct JSON):
45
+ Example usage:
42
46
  \`\`\`
43
47
  {
48
+ "repositoryPath": "/Users/dev/my-api",
44
49
  "analysisReport": "<RepositoryAnalysis JSON from skyramp_analyze_repository>",
45
50
  "customWeights": {
46
51
  "load": 1.5,
@@ -49,13 +54,10 @@ Example usage (direct JSON):
49
54
  }
50
55
  \`\`\`
51
56
 
52
- **CRITICAL RULES**:
53
- - Return the JSON object directly in your response text.
54
- - DO NOT create or save any files (no test_mapping.json or .json/.md files).
55
- - DO NOT use file write tools or save analysis to disk.
56
- - Output the complete analysis JSON inline in your response.
57
+ **OUTPUT**:
58
+ Returns the state file path to pass to skyramp_recommend_tests
57
59
 
58
- Output: TestMappingResult JSON with priority scores, feasibility, and reasoning for each test type.`,
60
+ Output: TestMappingResult with priority scores, and state file path for next step.`,
59
61
  inputSchema: mapTestsSchema.shape,
60
62
  }, async (params) => {
61
63
  let errorResult;
@@ -142,14 +144,23 @@ Output: TestMappingResult JSON with priority scores, feasibility, and reasoning
142
144
  contextFactors,
143
145
  summary: { highPriority, mediumPriority, lowPriority },
144
146
  };
147
+ // Save results using StateManager (stores in /tmp)
148
+ const stateManager = new StateManager("recommendation");
149
+ const stateData = {
150
+ repositoryPath: params.repositoryPath,
151
+ analysis,
152
+ mapping,
153
+ };
154
+ await stateManager.writeData(stateData, {
155
+ repositoryPath: params.repositoryPath,
156
+ step: "map-tests",
157
+ });
158
+ const stateFilePath = stateManager.getStatePath();
159
+ const sessionId = stateManager.getSessionId();
160
+ const stateSize = await stateManager.getSizeFormatted();
161
+ logger.info(`Saved test mapping to: ${stateFilePath} (${stateSize})`);
145
162
  // Format output
146
- const output = `⚠️ CRITICAL INSTRUCTION - READ FIRST:
147
- - Return the JSON object directly in your response text.
148
- - DO NOT create or save any files (no repository_analysis.json or .md files).
149
- - DO NOT use file write tools or save analysis to disk.
150
- - Output the complete analysis JSON inline in your response.
151
-
152
- # Test Priority Mapping
163
+ const output = `# Test Priority Mapping
153
164
 
154
165
  ## Summary
155
166
  - **High Priority**: ${mapping.summary.highPriority.join(", ") || "None"}
@@ -173,13 +184,12 @@ ${mapping.contextFactors.applied
173
184
  .map((factor) => `- **${factor.factor}**: ${factor.impact} (×${factor.multiplier})`)
174
185
  .join("\n")}
175
186
 
176
- ## Complete Mapping Result (JSON)
187
+ ## Results Saved
177
188
 
178
- \`\`\`json
179
- ${JSON.stringify(mapping, null, 2)}
180
- \`\`\`
189
+ **State File**: \`${stateFilePath}\`
190
+ **Session ID**: \`${sessionId}\`
181
191
 
182
- **Next Step**: Use \`skyramp_recommend_tests\` with this mapping result to get actionable test recommendations.`;
192
+ **Next Step**: Call \`skyramp_recommend_tests\` with stateFile: \`${stateFilePath}\``;
183
193
  return {
184
194
  content: [
185
195
  {
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
- import { testMappingResultSchema } from "../../types/TestMapping.js";
3
- import { repositoryAnalysisSchema } from "../../types/RepositoryAnalysis.js";
4
2
  import { getTestRecommendationPrompt } from "../../prompts/test-recommendation/test-recommendation-prompt.js";
3
+ import { StateManager, } from "../../utils/AnalysisStateManager.js";
5
4
  import { logger } from "../../utils/logger.js";
6
5
  import { AnalyticsService } from "../../services/AnalyticsService.js";
7
6
  /**
@@ -9,12 +8,9 @@ import { AnalyticsService } from "../../services/AnalyticsService.js";
9
8
  * MCP tool for generating actionable test recommendations
10
9
  */
11
10
  const recommendTestsSchema = z.object({
12
- priorityScores: z
13
- .union([z.string(), testMappingResultSchema])
14
- .describe("Test mapping result (JSON string or object from skyramp_map_tests)"),
15
- analysisReport: z
16
- .union([z.string(), repositoryAnalysisSchema])
17
- .describe("Repository analysis result (JSON string or object from skyramp_analyze_repository)"),
11
+ stateFile: z
12
+ .string()
13
+ .describe("Path to state file from skyramp_map_tests (contains both analysis and mapping results)"),
18
14
  topN: z
19
15
  .number()
20
16
  .default(7)
@@ -29,7 +25,9 @@ export function registerRecommendTestsTool(server) {
29
25
  server.registerTool(TOOL_NAME, {
30
26
  description: `Generate actionable test recommendations with ready-to-use generation prompts.
31
27
 
32
- This tool takes test priority and repository analysis to create:
28
+ **PREREQUISITE**: Call skyramp_map_tests first to get the stateFile path.
29
+
30
+ This tool reads the saved test mapping results and generates:
33
31
  - Ordered list of recommended test types (high/medium/low priority)
34
32
  - Specific test scenarios based on actual repository endpoints and flows
35
33
  - Artifact availability status and guidance for missing artifacts
@@ -56,41 +54,37 @@ For each recommended test type, you'll get:
56
54
  - THE PRIORITY SHOULD ONLY BE DEFINED AS HIGH, MEDIUM, OR LOW NOTHING ELSE.
57
55
  - DO NOT SHOW ANY PRIORITY BREAKDOWN IN THE OUTPUT.
58
56
  - DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
59
- - DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
60
57
 
61
- Output: TestRecommendation JSON with prioritized, actionable test recommendations.
58
+ Output: TestRecommendation with prioritized, actionable test recommendations.
62
59
  **CRITICAL:** At the end of the tool execution, MUST display the below message:
63
60
  ** This tool is currently in Early Preview stage. Please verify the results. **`,
64
61
  inputSchema: recommendTestsSchema.shape,
65
62
  }, async (params) => {
66
63
  let errorResult;
67
- let prompt;
68
64
  try {
69
65
  logger.info("Recommend tests tool invoked", {
66
+ stateFile: params.stateFile,
70
67
  topN: params.topN,
71
68
  minScore: params.minScore,
72
69
  });
73
- // Parse inputs if they're strings
74
- let mapping = params.priorityScores;
75
- let analysis = params.analysisReport;
76
- if (typeof mapping === "string") {
77
- try {
78
- mapping = JSON.parse(mapping);
79
- }
80
- catch (error) {
81
- throw new Error("priorityScores must be valid JSON string or TestMappingResult object");
82
- }
70
+ // Read state file using StateManager
71
+ if (!params.stateFile) {
72
+ throw new Error("stateFile is required");
73
+ }
74
+ const stateManager = StateManager.fromStatePath(params.stateFile);
75
+ if (!stateManager.exists()) {
76
+ throw new Error(`State file not found: ${params.stateFile}. Run skyramp_map_tests first.`);
83
77
  }
84
- if (typeof analysis === "string") {
85
- try {
86
- analysis = JSON.parse(analysis);
87
- }
88
- catch (error) {
89
- throw new Error("analysisReport must be valid JSON string or RepositoryAnalysis object");
90
- }
78
+ const stateData = await stateManager.readData();
79
+ if (!stateData) {
80
+ throw new Error(`Failed to read state file: ${params.stateFile}. Run skyramp_map_tests first.`);
81
+ }
82
+ const { analysis, mapping, repositoryPath } = stateData;
83
+ if (!analysis || !mapping) {
84
+ throw new Error("State file is missing required data (analysis or mapping)");
91
85
  }
92
86
  // Generate the prompt for LLM to create dynamic recommendations
93
- prompt = getTestRecommendationPrompt(mapping, analysis, params.topN);
87
+ const prompt = getTestRecommendationPrompt(mapping, analysis, params.topN);
94
88
  // Return prompt for LLM to execute
95
89
  return {
96
90
  content: [
@@ -98,6 +92,9 @@ Output: TestRecommendation JSON with prioritized, actionable test recommendation
98
92
  type: "text",
99
93
  text: `# Test Recommendation Request
100
94
 
95
+ **State File**: \`${params.stateFile}\`
96
+ **Repository**: \`${repositoryPath}\`
97
+
101
98
  Please generate actionable test recommendations based on the priority scores and repository analysis.
102
99
 
103
100
  ${prompt}
@@ -3,8 +3,9 @@ import { SkyrampClient } from "@skyramp/skyramp";
3
3
  import openProxyTerminalTracked from "../../utils/proxy-terminal.js";
4
4
  import { TELEMETRY_entrypoint_FIELD_NAME } from "../../utils/utils.js";
5
5
  import { logger } from "../../utils/logger.js";
6
- import { basePlaywrightSchema } from "../../types/TestTypes.js";
6
+ import { basePlaywrightSchema, baseSchema, DEFAULT_SESSION_STORAGE_PATH, SESSION_STORAGE_FILENAME, } from "../../types/TestTypes.js";
7
7
  import { AnalyticsService } from "../../services/AnalyticsService.js";
8
+ import path from "path";
8
9
  const TOOL_NAME = "skyramp_start_trace_collection";
9
10
  export function registerTraceTool(server) {
10
11
  server.registerTool(TOOL_NAME, {
@@ -30,6 +31,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
30
31
  .describe("Whether to enable Playwright for trace collection. Set to true for UI interactions, false for API-only tracing")
31
32
  .default(true),
32
33
  playwrightStoragePath: basePlaywrightSchema.shape.playwrightStoragePath,
34
+ playwrightSaveStoragePath: basePlaywrightSchema.shape.playwrightSaveStoragePath,
33
35
  playwrightViewportSize: basePlaywrightSchema.shape.playwrightViewportSize,
34
36
  runtime: z
35
37
  .string()
@@ -55,6 +57,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
55
57
  .number()
56
58
  .default(35142)
57
59
  .describe("Port number for the Docker worker service during trace collection"),
60
+ outputDir: baseSchema.shape.outputDir,
58
61
  prompt: z
59
62
  .string()
60
63
  .describe("The prompt user provided to start trace collection"),
@@ -62,8 +65,24 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
62
65
  _meta: {
63
66
  keywords: ["start trace", "trace collection", "trace generation"],
64
67
  },
65
- }, async (params) => {
68
+ }, async (params, extra) => {
66
69
  let errorResult;
70
+ // Helper to send progress notifications to the MCP client
71
+ const sendProgress = async (progress, total, message) => {
72
+ const progressToken = extra._meta?.progressToken;
73
+ if (progressToken !== undefined) {
74
+ const notification = {
75
+ method: "notifications/progress",
76
+ params: {
77
+ progressToken,
78
+ progress,
79
+ total,
80
+ message,
81
+ },
82
+ };
83
+ await extra.sendNotification(notification);
84
+ }
85
+ };
67
86
  logger.info("Starting trace collection", {
68
87
  playwright: params.playwright,
69
88
  runtime: params.runtime,
@@ -72,25 +91,57 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
72
91
  noProxy: params.noProxy,
73
92
  dockerNetwork: params.dockerNetwork,
74
93
  dockerWorkerPort: params.dockerWorkerPort,
94
+ outputDir: params.outputDir,
75
95
  prompt: params.prompt,
76
96
  });
77
- const client = new SkyrampClient();
78
- const generateOptions = {
79
- testType: "trace",
80
- runtime: params.runtime,
81
- dockerNetwork: params.dockerNetwork,
82
- dockerWorkerPort: params.dockerWorkerPort.toString(),
83
- generateInclude: params.include,
84
- generateExclude: params.exclude,
85
- generateNoProxy: params.noProxy,
86
- unblock: true,
87
- playwright: params.playwright,
88
- playwrightStoragePath: params.playwrightStoragePath,
89
- playwrightViewportSize: params.playwrightViewportSize,
90
- entrypoint: TELEMETRY_entrypoint_FIELD_NAME,
91
- };
97
+ // Resolve playwrightSaveStoragePath to outputDir if it's the default relative path
98
+ let saveStoragePath = params.playwrightSaveStoragePath;
99
+ if (params.outputDir &&
100
+ saveStoragePath === DEFAULT_SESSION_STORAGE_PATH) {
101
+ saveStoragePath = path.join(params.outputDir, SESSION_STORAGE_FILENAME);
102
+ }
92
103
  try {
93
- const result = await client.generateRestTest(generateOptions);
104
+ // Send initial progress
105
+ await sendProgress(0, 100, "Initializing trace collection...");
106
+ const client = new SkyrampClient();
107
+ const generateOptions = {
108
+ testType: "trace",
109
+ runtime: params.runtime,
110
+ dockerNetwork: params.dockerNetwork,
111
+ dockerWorkerPort: params.dockerWorkerPort.toString(),
112
+ generateInclude: params.include,
113
+ generateExclude: params.exclude,
114
+ generateNoProxy: params.noProxy,
115
+ unblock: true,
116
+ playwright: params.playwright,
117
+ playwrightStoragePath: params.playwrightStoragePath,
118
+ playwrightViewportSize: params.playwrightViewportSize,
119
+ entrypoint: TELEMETRY_entrypoint_FIELD_NAME,
120
+ };
121
+ // Send progress for configuration
122
+ const traceMode = params.playwright ? "UI + Backend" : "Backend-only";
123
+ await sendProgress(30, 100, `Configuring ${traceMode} trace collection...`);
124
+ // Start periodic progress updates during the long-running operation
125
+ const startTime = Date.now();
126
+ let progressTick = 0;
127
+ const progressInterval = setInterval(() => {
128
+ progressTick++;
129
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
130
+ // Progress moves from 30% to 65% during service startup
131
+ const percent = Math.min(30 + progressTick * 5, 65);
132
+ sendProgress(percent, 100, `Starting trace collection service... (${elapsed}s elapsed)`).catch(() => {
133
+ // Ignore progress notification errors
134
+ });
135
+ // Also log to server for visibility
136
+ logger.info(`Trace collection progress: ${percent}% (${elapsed}s elapsed)`);
137
+ }, 10000); // 10 seconds
138
+ let result;
139
+ try {
140
+ result = await client.generateRestTest(generateOptions);
141
+ }
142
+ finally {
143
+ clearInterval(progressInterval);
144
+ }
94
145
  if (result.toLowerCase().includes("failed")) {
95
146
  errorResult = {
96
147
  content: [
@@ -103,7 +154,11 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
103
154
  };
104
155
  return errorResult;
105
156
  }
157
+ // Send progress for opening proxy terminal
158
+ await sendProgress(70, 100, "Opening proxy terminal...");
106
159
  await openProxyTerminalTracked();
160
+ // Send completion progress
161
+ await sendProgress(100, 100, "Trace collection started successfully");
107
162
  errorResult = {
108
163
  content: [
109
164
  {
@@ -67,7 +67,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
67
67
  if (!existsSync(params.outputDir)) {
68
68
  mkdirSync(params.outputDir, { recursive: true });
69
69
  }
70
- let errList = {
70
+ const errList = {
71
71
  content: [],
72
72
  isError: true,
73
73
  };
@@ -1,4 +1,6 @@
1
1
  import { z } from "zod";
2
+ export const SESSION_STORAGE_FILENAME = "skyramp_session_storage.json";
3
+ export const DEFAULT_SESSION_STORAGE_PATH = `./${SESSION_STORAGE_FILENAME}`;
2
4
  export var TestType;
3
5
  (function (TestType) {
4
6
  TestType["SMOKE"] = "smoke";
@@ -86,7 +88,11 @@ export const basePlaywrightSchema = z.object({
86
88
  playwrightStoragePath: z
87
89
  .string()
88
90
  .optional()
89
- .describe("Path to playwright session storage for trace collection with playwright enabled. THE PATH MUST BE AN ABSOLUTE PATH LIKE /Users/<path>/<filename>.json"),
91
+ .describe("Path to playwright session storage to load for trace collection with playwright enabled. THE PATH MUST BE AN ABSOLUTE PATH LIKE /Users/<path>/<filename>.json"),
92
+ playwrightSaveStoragePath: z
93
+ .string()
94
+ .default(DEFAULT_SESSION_STORAGE_PATH)
95
+ .describe(`Path to SAVE Playwright session storage after trace collection. When user says 'with session storage' or 'save session' without specifying a path, use the default '${DEFAULT_SESSION_STORAGE_PATH}'. This SAVES authentication state (cookies, localStorage, sessionStorage) when the browser closes. To LOAD existing auth state, use playwrightStoragePath instead. Can be relative (e.g., 'auth.json') or absolute path.`),
90
96
  playwrightViewportSize: z
91
97
  .string()
92
98
  .default("")