@skyramp/mcp 0.0.46 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +18 -11
- package/build/prompts/test-recommendation/repository-analysis-prompt.js +7 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +7 -1
- package/build/prompts/testGenerationPrompt.js +1 -0
- package/build/services/AnalyticsService.js +0 -8
- package/build/services/TestExecutionService.js +138 -20
- package/build/tools/executeSkyrampTestTool.js +28 -2
- package/build/tools/generate-tests/generateContractRestTool.js +3 -1
- package/build/tools/generate-tests/generateFuzzRestTool.js +3 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.js +3 -1
- package/build/tools/generate-tests/generateLoadRestTool.js +3 -1
- package/build/tools/generate-tests/generateScenarioRestTool.js +3 -1
- package/build/tools/generate-tests/generateSmokeRestTool.js +3 -1
- package/build/tools/generate-tests/generateUIRestTool.js +2 -1
- package/build/tools/test-recommendation/analyzeRepositoryTool.js +7 -2
- package/build/tools/test-recommendation/mapTestsTool.js +12 -3
- package/build/tools/test-recommendation/recommendTestsTool.js +2 -0
- package/build/types/TestTypes.js +17 -3
- package/build/utils/logger.js +6 -2
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -127,19 +127,26 @@ async function main() {
|
|
|
127
127
|
await server.connect(transport);
|
|
128
128
|
logger.info("MCP Server started successfully");
|
|
129
129
|
// Listen for stdin closure (parent process disconnected)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
// Using multiple events for robustness across different disconnection scenarios
|
|
131
|
+
let isShuttingDown = false;
|
|
132
|
+
const handleParentDisconnect = (event) => {
|
|
133
|
+
if (isShuttingDown)
|
|
134
|
+
return;
|
|
135
|
+
isShuttingDown = true;
|
|
136
|
+
logger.info(`Parent process disconnected (${event}). Cleaning up and exiting...`);
|
|
137
|
+
server.close().finally(() => {
|
|
138
|
+
process.exit(0);
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
process.stdin.on("end", () => handleParentDisconnect("stdin end"));
|
|
142
|
+
process.stdin.on("close", () => handleParentDisconnect("stdin close"));
|
|
143
|
+
process.stdin.on("error", (err) => {
|
|
144
|
+
logger.error("STDIN error, parent likely disconnected", { error: err });
|
|
145
|
+
handleParentDisconnect("stdin error");
|
|
133
146
|
});
|
|
134
147
|
// Handle process termination signals
|
|
135
|
-
process.on("SIGTERM", () =>
|
|
136
|
-
|
|
137
|
-
process.exit(0);
|
|
138
|
-
});
|
|
139
|
-
process.on("SIGINT", () => {
|
|
140
|
-
logger.info("Received SIGINT, shutting down gracefully...");
|
|
141
|
-
process.exit(0);
|
|
142
|
-
});
|
|
148
|
+
process.on("SIGTERM", () => handleParentDisconnect("SIGTERM"));
|
|
149
|
+
process.on("SIGINT", () => handleParentDisconnect("SIGINT"));
|
|
143
150
|
}
|
|
144
151
|
main().catch(async (error) => {
|
|
145
152
|
logger.critical("Fatal error in main()", {
|
|
@@ -282,6 +282,12 @@ VALIDATION CHECKLIST:
|
|
|
282
282
|
- [ ] Infrastructure flags verified
|
|
283
283
|
- [ ] Existing tests catalogued
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
**CRITICAL INSTRUCTIONS**:
|
|
286
|
+
- Return the JSON object directly in your response text.
|
|
287
|
+
- DO NOT create or save any files (no repository_analysis.json or .md files).
|
|
288
|
+
- DO NOT use file write tools or save analysis to disk.
|
|
289
|
+
- Output the complete analysis JSON inline in your response.
|
|
290
|
+
|
|
291
|
+
Begin analysis now. Return the JSON object directly in your response text.
|
|
286
292
|
`;
|
|
287
293
|
}
|
|
@@ -120,6 +120,12 @@ Return a JSON object with this structure:
|
|
|
120
120
|
4. **Provide Guidance**: Clear instructions for creating missing artifacts
|
|
121
121
|
5. **Include Evidence**: Reference specific repository characteristics in rationale
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
**CRITICAL INSTRUCTIONS**:
|
|
124
|
+
- Return the JSON object directly in your response text.
|
|
125
|
+
- DO NOT create or save any files (no test_recommendations.md, .json, or other files).
|
|
126
|
+
- DO NOT use file write tools or save recommendations to disk.
|
|
127
|
+
- Output the complete recommendations JSON inline in your response.
|
|
128
|
+
|
|
129
|
+
Generate recommendations now. Return the JSON object directly in your response text.
|
|
124
130
|
`;
|
|
125
131
|
}
|
|
@@ -17,6 +17,7 @@ export function registerTestGenerationPrompt(mcpServer) {
|
|
|
17
17
|
- ALWAYS SHOW STEPS TO GENERATE TEST USING MCP TOOLS AND NEVER SHOW THE CLI COMMANDS.
|
|
18
18
|
- **CRITICAL: ONLY UI, INTEGRATION, E2E, LOAD TESTS GENERATED FROM TRACES MUST BE MODULARIZED USING skyramp_modularization TOOL. ADD A TASK TO MODULARIZE THE TEST USING skyramp_modularization TOOL AFTER GENERATING THESE(UI, INTEGRATION, E2E, LOAD) TESTS. DO NOT MODULARIZE THESE TESTS IF THEY ARE NOT GENERATED FROM TRACES.**
|
|
19
19
|
- **CRITICAL: skyramp_reuse_code TOOL MUST BE CALLED IF DURING THE TEST GENERATION THE CODE REUSE FLAG IS SET TO TRUE EXPLICITLY BY THE USER AND THE TEST IS GENERATED FROM TRACES.**
|
|
20
|
+
- **CRITICAL: DO NOT READ apiSchema FILES (OpenAPI/Swagger specifications). These files can be very large (often several MB). Simply pass the file path or URL to the test generation tool - the backend will handle reading and processing it. Never use file reading tools on apiSchema parameters.**
|
|
20
21
|
|
|
21
22
|
**MANDATORY RULES**:
|
|
22
23
|
1. **Priority Scores Must Remain Unchanged**: When a test type is missing required inputs (e.g., Playwright recordings, traces), **DO NOT**:
|
|
@@ -31,14 +31,6 @@ export class AnalyticsService {
|
|
|
31
31
|
errorMessage: errorMessage,
|
|
32
32
|
analyticsResult: params,
|
|
33
33
|
});
|
|
34
|
-
// console.error(
|
|
35
|
-
// "tool name ::: ",
|
|
36
|
-
// toolName,
|
|
37
|
-
// "params ::: ",
|
|
38
|
-
// params,
|
|
39
|
-
// "error message ::: ",
|
|
40
|
-
// errorMessage,
|
|
41
|
-
// );
|
|
42
34
|
await pushToolEvent(this.entryPoint, toolName, errorMessage, params);
|
|
43
35
|
}
|
|
44
36
|
/**
|
|
@@ -6,8 +6,9 @@ import { stripVTControlCharacters } from "util";
|
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
7
|
const DEFAULT_TIMEOUT = 300000; // 5 minutes
|
|
8
8
|
const MAX_CONCURRENT_EXECUTIONS = 5;
|
|
9
|
-
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.
|
|
9
|
+
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.0";
|
|
10
10
|
const DOCKER_PLATFORM = "linux/amd64";
|
|
11
|
+
const EXECUTION_PROGRESS_INTERVAL = 10000; // 10 seconds between progress updates during execution
|
|
11
12
|
// Files and directories to exclude when mounting workspace to Docker container
|
|
12
13
|
export const EXCLUDED_MOUNT_ITEMS = [
|
|
13
14
|
"package-lock.json",
|
|
@@ -24,13 +25,13 @@ function findCommentStart(line) {
|
|
|
24
25
|
let escaped = false;
|
|
25
26
|
for (let i = 0; i < line.length; i++) {
|
|
26
27
|
const char = line[i];
|
|
27
|
-
const nextChar = i + 1 < line.length ? line[i + 1] :
|
|
28
|
+
const nextChar = i + 1 < line.length ? line[i + 1] : "";
|
|
28
29
|
// Handle escape sequences
|
|
29
30
|
if (escaped) {
|
|
30
31
|
escaped = false;
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
if (char ===
|
|
34
|
+
if (char === "\\") {
|
|
34
35
|
escaped = true;
|
|
35
36
|
continue;
|
|
36
37
|
}
|
|
@@ -45,10 +46,10 @@ function findCommentStart(line) {
|
|
|
45
46
|
}
|
|
46
47
|
// Check for comments only if not in a string
|
|
47
48
|
if (!inSingleQuote && !inDoubleQuote) {
|
|
48
|
-
if (char ===
|
|
49
|
+
if (char === "/" && nextChar === "/") {
|
|
49
50
|
return i;
|
|
50
51
|
}
|
|
51
|
-
if (char ===
|
|
52
|
+
if (char === "#") {
|
|
52
53
|
return i;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -87,8 +88,8 @@ function filterComments(lines) {
|
|
|
87
88
|
continue;
|
|
88
89
|
}
|
|
89
90
|
// Check for multi-line comment start/end (/* */)
|
|
90
|
-
if (trimmed.includes(
|
|
91
|
-
if (trimmed.includes(
|
|
91
|
+
if (trimmed.includes("/*")) {
|
|
92
|
+
if (trimmed.includes("*/")) {
|
|
92
93
|
// Single-line multi-line comment (e.g., /* comment */)
|
|
93
94
|
// Don't change state, just skip the line
|
|
94
95
|
continue;
|
|
@@ -100,13 +101,13 @@ function filterComments(lines) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
if (inMultiLineComment) {
|
|
103
|
-
if (trimmed.includes(
|
|
104
|
+
if (trimmed.includes("*/")) {
|
|
104
105
|
inMultiLineComment = false;
|
|
105
106
|
}
|
|
106
107
|
continue;
|
|
107
108
|
}
|
|
108
109
|
// Skip single-line comments
|
|
109
|
-
if (trimmed.startsWith(
|
|
110
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#")) {
|
|
110
111
|
continue;
|
|
111
112
|
}
|
|
112
113
|
// Remove inline comments from the line before processing
|
|
@@ -127,7 +128,7 @@ function filterComments(lines) {
|
|
|
127
128
|
function detectSessionFiles(testFilePath) {
|
|
128
129
|
try {
|
|
129
130
|
const content = fs.readFileSync(testFilePath, "utf-8");
|
|
130
|
-
const lines = content.split(
|
|
131
|
+
const lines = content.split("\n");
|
|
131
132
|
const sessionFiles = [];
|
|
132
133
|
// Pattern for TypeScript/JavaScript: storageState: '/path/to/file' or storageState: "/path/to/file"
|
|
133
134
|
const tsJsPattern = /storageState:\s*['"]([^'"]+)['"]/g;
|
|
@@ -164,16 +165,17 @@ function detectSessionFiles(testFilePath) {
|
|
|
164
165
|
return sessionFiles;
|
|
165
166
|
}
|
|
166
167
|
catch (error) {
|
|
167
|
-
logger.error(`Failed to detect session files in ${testFilePath}`, {
|
|
168
|
+
logger.error(`Failed to detect session files in ${testFilePath}`, {
|
|
169
|
+
error,
|
|
170
|
+
});
|
|
168
171
|
return [];
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
export class TestExecutionService {
|
|
172
175
|
docker;
|
|
173
|
-
imageReady;
|
|
176
|
+
imageReady = null;
|
|
174
177
|
constructor() {
|
|
175
178
|
this.docker = new Docker();
|
|
176
|
-
this.imageReady = this.ensureDockerImage();
|
|
177
179
|
}
|
|
178
180
|
/**
|
|
179
181
|
* Execute multiple tests in parallel batches
|
|
@@ -217,13 +219,42 @@ export class TestExecutionService {
|
|
|
217
219
|
}
|
|
218
220
|
/**
|
|
219
221
|
* Execute a single test
|
|
222
|
+
* @param options Test execution options
|
|
223
|
+
* @param onProgress Optional callback for progress updates
|
|
220
224
|
*/
|
|
221
|
-
async executeTest(options) {
|
|
225
|
+
async executeTest(options, onProgress) {
|
|
222
226
|
const startTime = Date.now();
|
|
223
227
|
const executedAt = new Date().toISOString();
|
|
224
228
|
logger.debug(`Executing test: ${options.testFile}`);
|
|
225
|
-
//
|
|
226
|
-
await
|
|
229
|
+
// Check and ensure Docker image is ready with progress reporting
|
|
230
|
+
await onProgress?.({
|
|
231
|
+
phase: "docker-check",
|
|
232
|
+
message: "Checking Docker image availability...",
|
|
233
|
+
percent: 5,
|
|
234
|
+
});
|
|
235
|
+
const imageResult = await this.ensureDockerImage(onProgress);
|
|
236
|
+
if (imageResult.cached) {
|
|
237
|
+
await onProgress?.({
|
|
238
|
+
phase: "docker-check",
|
|
239
|
+
message: "Using cached Docker image",
|
|
240
|
+
percent: 20,
|
|
241
|
+
details: { cached: true },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
await onProgress?.({
|
|
246
|
+
phase: "docker-pull",
|
|
247
|
+
message: "Docker image pull completed",
|
|
248
|
+
percent: 20,
|
|
249
|
+
details: { cached: false },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
// Report preparing phase
|
|
253
|
+
await onProgress?.({
|
|
254
|
+
phase: "preparing",
|
|
255
|
+
message: "Validating workspace and test file...",
|
|
256
|
+
percent: 25,
|
|
257
|
+
});
|
|
227
258
|
// Validate workspace path - use accessSync for better validation
|
|
228
259
|
const workspacePath = path.resolve(options.workspacePath);
|
|
229
260
|
try {
|
|
@@ -240,6 +271,11 @@ export class TestExecutionService {
|
|
|
240
271
|
if (!fs.existsSync(options.testFile)) {
|
|
241
272
|
throw new Error(`Test file does not exist: ${options.testFile}`);
|
|
242
273
|
}
|
|
274
|
+
await onProgress?.({
|
|
275
|
+
phase: "preparing",
|
|
276
|
+
message: "Preparing Docker container configuration...",
|
|
277
|
+
percent: 30,
|
|
278
|
+
});
|
|
243
279
|
const containerMountPath = "/home/user";
|
|
244
280
|
const dockerSocketPath = "/var/run/docker.sock";
|
|
245
281
|
// Calculate relative test file path
|
|
@@ -330,8 +366,34 @@ export class TestExecutionService {
|
|
|
330
366
|
}
|
|
331
367
|
const stream = new DockerStream();
|
|
332
368
|
try {
|
|
369
|
+
// Report executing phase
|
|
370
|
+
await onProgress?.({
|
|
371
|
+
phase: "executing",
|
|
372
|
+
message: "Starting test execution in Docker container...",
|
|
373
|
+
percent: 40,
|
|
374
|
+
});
|
|
333
375
|
let statusCode = 0;
|
|
334
376
|
let containerRef = null;
|
|
377
|
+
// Start periodic progress reporter during execution
|
|
378
|
+
// Runs in parallel with docker.run() to keep the AI agent informed
|
|
379
|
+
let progressIntervalHandle;
|
|
380
|
+
if (onProgress) {
|
|
381
|
+
let progressTick = 0;
|
|
382
|
+
progressIntervalHandle = setInterval(() => {
|
|
383
|
+
progressTick++;
|
|
384
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
385
|
+
// Progress moves from 40% to 79% during execution phase
|
|
386
|
+
// Slowly increment to show activity without reaching 80% (reserved for processing)
|
|
387
|
+
const percent = Math.min(40 + progressTick * 2, 79);
|
|
388
|
+
onProgress({
|
|
389
|
+
phase: "executing",
|
|
390
|
+
message: `Test running... (${elapsed}s elapsed)`,
|
|
391
|
+
percent,
|
|
392
|
+
}).catch(() => {
|
|
393
|
+
// Ignore progress notification errors
|
|
394
|
+
});
|
|
395
|
+
}, EXECUTION_PROGRESS_INTERVAL);
|
|
396
|
+
}
|
|
335
397
|
// Run container with timeout
|
|
336
398
|
const executionPromise = this.docker
|
|
337
399
|
.run(EXECUTOR_DOCKER_IMAGE, command, stream, {
|
|
@@ -367,14 +429,18 @@ export class TestExecutionService {
|
|
|
367
429
|
});
|
|
368
430
|
try {
|
|
369
431
|
statusCode = await Promise.race([executionPromise, timeoutPromise]);
|
|
370
|
-
// Clear
|
|
432
|
+
// Clear timers on successful completion
|
|
371
433
|
if (timeoutHandle)
|
|
372
434
|
clearTimeout(timeoutHandle);
|
|
435
|
+
if (progressIntervalHandle)
|
|
436
|
+
clearInterval(progressIntervalHandle);
|
|
373
437
|
}
|
|
374
438
|
catch (error) {
|
|
375
|
-
// Clear
|
|
439
|
+
// Clear all timers on error
|
|
376
440
|
if (timeoutHandle)
|
|
377
441
|
clearTimeout(timeoutHandle);
|
|
442
|
+
if (progressIntervalHandle)
|
|
443
|
+
clearInterval(progressIntervalHandle);
|
|
378
444
|
// Cleanup container on timeout or other errors
|
|
379
445
|
if (containerRef !== null) {
|
|
380
446
|
const container = containerRef;
|
|
@@ -392,6 +458,12 @@ export class TestExecutionService {
|
|
|
392
458
|
}
|
|
393
459
|
throw error; // Re-throw the original error
|
|
394
460
|
}
|
|
461
|
+
// Report processing phase
|
|
462
|
+
await onProgress?.({
|
|
463
|
+
phase: "processing",
|
|
464
|
+
message: "Test execution completed, processing results...",
|
|
465
|
+
percent: 80,
|
|
466
|
+
});
|
|
395
467
|
const duration = Date.now() - startTime;
|
|
396
468
|
const cleanOutput = stripVTControlCharacters(output);
|
|
397
469
|
logger.info("Test execution completed", {
|
|
@@ -415,6 +487,14 @@ export class TestExecutionService {
|
|
|
415
487
|
output: cleanOutput,
|
|
416
488
|
exitCode: statusCode,
|
|
417
489
|
};
|
|
490
|
+
// Report completion
|
|
491
|
+
await onProgress?.({
|
|
492
|
+
phase: "processing",
|
|
493
|
+
message: result.passed
|
|
494
|
+
? "Test passed successfully"
|
|
495
|
+
: "Test execution completed with failures",
|
|
496
|
+
percent: 100,
|
|
497
|
+
});
|
|
418
498
|
logger.debug(`Test ${result.passed ? "passed" : "failed"}: ${options.testFile}`);
|
|
419
499
|
return result;
|
|
420
500
|
}
|
|
@@ -436,17 +516,25 @@ export class TestExecutionService {
|
|
|
436
516
|
}
|
|
437
517
|
/**
|
|
438
518
|
* Ensure Docker image is available
|
|
519
|
+
* @param onProgress Optional callback for progress updates during pull
|
|
520
|
+
* @returns Object indicating whether image was cached or pulled
|
|
439
521
|
*/
|
|
440
|
-
async ensureDockerImage() {
|
|
522
|
+
async ensureDockerImage(onProgress) {
|
|
441
523
|
try {
|
|
442
524
|
const images = await this.docker.listImages();
|
|
443
525
|
const imageExists = images.some((img) => (img.RepoTags && img.RepoTags.includes(EXECUTOR_DOCKER_IMAGE)) ||
|
|
444
526
|
(img.RepoDigests && img.RepoDigests.includes(EXECUTOR_DOCKER_IMAGE)));
|
|
445
527
|
if (imageExists) {
|
|
446
528
|
logger.debug(`Docker image ${EXECUTOR_DOCKER_IMAGE} already available`);
|
|
447
|
-
return;
|
|
529
|
+
return { cached: true };
|
|
448
530
|
}
|
|
449
531
|
logger.info(`Pulling Docker image ${EXECUTOR_DOCKER_IMAGE}...`);
|
|
532
|
+
await onProgress?.({
|
|
533
|
+
phase: "docker-pull",
|
|
534
|
+
message: `Pulling Docker image ${EXECUTOR_DOCKER_IMAGE}...`,
|
|
535
|
+
percent: 10,
|
|
536
|
+
details: { cached: false },
|
|
537
|
+
});
|
|
450
538
|
await new Promise((resolve, reject) => {
|
|
451
539
|
this.docker.pull(EXECUTOR_DOCKER_IMAGE, { platform: DOCKER_PLATFORM }, (err, stream) => {
|
|
452
540
|
if (err)
|
|
@@ -458,9 +546,39 @@ export class TestExecutionService {
|
|
|
458
546
|
return reject(err);
|
|
459
547
|
logger.info(`Docker image ${EXECUTOR_DOCKER_IMAGE} pulled successfully`);
|
|
460
548
|
resolve(res);
|
|
549
|
+
},
|
|
550
|
+
// Progress callback for each layer
|
|
551
|
+
(event) => {
|
|
552
|
+
if (event.status && onProgress) {
|
|
553
|
+
const progressMsg = event.progress || event.status;
|
|
554
|
+
const layerId = event.id || "";
|
|
555
|
+
// Calculate approximate progress (10-19% range during pull)
|
|
556
|
+
let percent = 10;
|
|
557
|
+
if (event.progressDetail?.current &&
|
|
558
|
+
event.progressDetail?.total) {
|
|
559
|
+
const layerProgress = event.progressDetail.current / event.progressDetail.total;
|
|
560
|
+
percent = 10 + Math.floor(layerProgress * 9); // 10-19%
|
|
561
|
+
}
|
|
562
|
+
onProgress({
|
|
563
|
+
phase: "docker-pull",
|
|
564
|
+
message: `${event.status}${layerId ? ` [${layerId}]` : ""}: ${progressMsg}`,
|
|
565
|
+
percent,
|
|
566
|
+
details: {
|
|
567
|
+
cached: false,
|
|
568
|
+
pullProgress: {
|
|
569
|
+
current: event.progressDetail?.current,
|
|
570
|
+
total: event.progressDetail?.total,
|
|
571
|
+
layer: layerId,
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
}).catch(() => {
|
|
575
|
+
// Ignore progress notification errors
|
|
576
|
+
});
|
|
577
|
+
}
|
|
461
578
|
});
|
|
462
579
|
});
|
|
463
580
|
});
|
|
581
|
+
return { cached: false };
|
|
464
582
|
}
|
|
465
583
|
catch (error) {
|
|
466
584
|
logger.error(`Failed to ensure Docker image: ${error.message}`);
|
|
@@ -50,17 +50,42 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
|
|
|
50
50
|
_meta: {
|
|
51
51
|
keywords: ["run test", "execute test"],
|
|
52
52
|
},
|
|
53
|
-
}, async (params) => {
|
|
53
|
+
}, async (params, extra) => {
|
|
54
54
|
let errorResult;
|
|
55
|
+
// Helper to send progress notifications to the MCP client
|
|
56
|
+
const sendProgress = async (progress, total, message) => {
|
|
57
|
+
const progressToken = extra._meta?.progressToken;
|
|
58
|
+
if (progressToken !== undefined) {
|
|
59
|
+
const notification = {
|
|
60
|
+
method: "notifications/progress",
|
|
61
|
+
params: {
|
|
62
|
+
progressToken,
|
|
63
|
+
progress,
|
|
64
|
+
total,
|
|
65
|
+
message,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
await extra.sendNotification(notification);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
// Progress callback adapter for TestExecutionService
|
|
72
|
+
const onExecutionProgress = async (progress) => {
|
|
73
|
+
await sendProgress(progress.percent, 100, progress.message);
|
|
74
|
+
};
|
|
55
75
|
try {
|
|
76
|
+
// Send initial progress
|
|
77
|
+
await sendProgress(0, 100, "Starting test execution...");
|
|
56
78
|
const executionService = new TestExecutionService();
|
|
79
|
+
// Execute test with progress callback - reports Docker cache/pull status
|
|
57
80
|
const result = await executionService.executeTest({
|
|
58
81
|
testFile: params.testFile,
|
|
59
82
|
workspacePath: params.workspacePath,
|
|
60
83
|
language: params.language,
|
|
61
84
|
testType: params.testType,
|
|
62
85
|
token: params.token,
|
|
63
|
-
});
|
|
86
|
+
}, onExecutionProgress);
|
|
87
|
+
// Progress is already reported by TestExecutionService
|
|
88
|
+
// Only report final status if not already at 100%
|
|
64
89
|
if (!result.passed) {
|
|
65
90
|
errorResult = {
|
|
66
91
|
content: [
|
|
@@ -74,6 +99,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
|
|
|
74
99
|
};
|
|
75
100
|
return errorResult;
|
|
76
101
|
}
|
|
102
|
+
// Success - progress already reported by TestExecutionService
|
|
77
103
|
return {
|
|
78
104
|
content: [
|
|
79
105
|
{
|
|
@@ -29,7 +29,9 @@ export function registerContractTestTool(server) {
|
|
|
29
29
|
server.registerTool(TOOL_NAME, {
|
|
30
30
|
description: `Generate a contract test using Skyramp's deterministic test generation platform.
|
|
31
31
|
|
|
32
|
-
Contract tests ensure your API implementation matches its OpenAPI/Swagger specification exactly. They validate request/response schemas, status codes, headers, and data types to prevent contract violations and API breaking changes
|
|
32
|
+
Contract tests ensure your API implementation matches its OpenAPI/Swagger specification exactly. They validate request/response schemas, status codes, headers, and data types to prevent contract violations and API breaking changes.
|
|
33
|
+
|
|
34
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**`,
|
|
33
35
|
inputSchema: contractTestSchema,
|
|
34
36
|
}, async (params) => {
|
|
35
37
|
const service = new ContractTestService();
|
|
@@ -25,7 +25,9 @@ export function registerFuzzTestTool(server) {
|
|
|
25
25
|
server.registerTool(TOOL_NAME, {
|
|
26
26
|
description: `Generate a fuzz test using Skyramp's deterministic test generation platform.
|
|
27
27
|
|
|
28
|
-
Fuzz tests improve application security and reliability by sending invalid, malformed, or unexpected data to your API endpoints. They help discover edge cases, input validation issues, error handling problems, and potential security vulnerabilities
|
|
28
|
+
Fuzz tests improve application security and reliability by sending invalid, malformed, or unexpected data to your API endpoints. They help discover edge cases, input validation issues, error handling problems, and potential security vulnerabilities.
|
|
29
|
+
|
|
30
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**`,
|
|
29
31
|
inputSchema: fuzzTestSchema,
|
|
30
32
|
_meta: {
|
|
31
33
|
keywords: ["negative test", "random test"],
|
|
@@ -42,7 +42,9 @@ export function registerIntegrationTestTool(server) {
|
|
|
42
42
|
server.registerTool(TOOL_NAME, {
|
|
43
43
|
description: `Generate an integration test using Skyramp's deterministic test generation platform.
|
|
44
44
|
|
|
45
|
-
Integration tests validate that multiple services, components, or modules work together correctly. They test complex user workflows, service interactions, data flow between systems, and ensure that integrated components function as expected in realistic scenarios
|
|
45
|
+
Integration tests validate that multiple services, components, or modules work together correctly. They test complex user workflows, service interactions, data flow between systems, and ensure that integrated components function as expected in realistic scenarios.
|
|
46
|
+
|
|
47
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**`,
|
|
46
48
|
inputSchema: integrationTestSchema,
|
|
47
49
|
}, async (params) => {
|
|
48
50
|
const service = new IntegrationTestService();
|
|
@@ -64,7 +64,9 @@ Load tests evaluate your application's performance, scalability, and stability u
|
|
|
64
64
|
- IF THE USER DOES NOT PROVIDE LOAD PARAMETERS, THEN USE DEFAULT VALUES FOR LOAD TESTS:
|
|
65
65
|
- loadDuration: "5" (5 seconds)
|
|
66
66
|
- loadNumThreads: "1" (1 thread)
|
|
67
|
-
- Other load parameters should remain empty unless explicitly specified by the user
|
|
67
|
+
- Other load parameters should remain empty unless explicitly specified by the user
|
|
68
|
+
|
|
69
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**`,
|
|
68
70
|
inputSchema: loadTestSchema,
|
|
69
71
|
_meta: {
|
|
70
72
|
keywords: ["load test", "performance test"],
|
|
@@ -8,7 +8,7 @@ const scenarioTestSchema = {
|
|
|
8
8
|
.describe("Name of the test scenario with multiple steps. Describe the complete workflow you want to test, including all actions and their sequence.KEEP IT SHORT AND DESCRIPTIVE AS USED FOR FILE NAME"),
|
|
9
9
|
destination: z
|
|
10
10
|
.string()
|
|
11
|
-
.describe("Destination
|
|
11
|
+
.describe("Destination hostname or IP address for the test (e.g., api.example.com, localhost, 192.168.1.1). Do NOT include port numbers."),
|
|
12
12
|
apiSchema: z
|
|
13
13
|
.string()
|
|
14
14
|
.describe("MUST be absolute path (/path/to/openapi.json) to the OpenAPI/Swagger schema file or a URL to the OpenAPI/Swagger schema file (e.g. https://demoshop.skyramp.dev/openapi.json). Required for accurate API mapping."),
|
|
@@ -77,6 +77,8 @@ The AI should parse the natural language scenario and provide:
|
|
|
77
77
|
- AI-parsed HTTP method and path (required)
|
|
78
78
|
- AI-parsed request/response bodies (optional)
|
|
79
79
|
|
|
80
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**
|
|
81
|
+
|
|
80
82
|
**Note:** This tool generates one request at a time. Call multiple times for multi-step scenarios.`,
|
|
81
83
|
inputSchema: scenarioTestSchema,
|
|
82
84
|
}, async (params) => {
|
|
@@ -25,7 +25,9 @@ export function registerSmokeTestTool(server) {
|
|
|
25
25
|
server.registerTool(TOOL_NAME, {
|
|
26
26
|
description: `Generate a smoke test using Skyramp's deterministic test generation platform.
|
|
27
27
|
|
|
28
|
-
Smoke testing is a preliminary check used to verify that an endpoint is accessible and returns a valid response. Smoke tests are useful for quickly identifying critical defects after significant changes. They provide rapid validation for basic functionality verification and endpoint accessibility testing
|
|
28
|
+
Smoke testing is a preliminary check used to verify that an endpoint is accessible and returns a valid response. Smoke tests are useful for quickly identifying critical defects after significant changes. They provide rapid validation for basic functionality verification and endpoint accessibility testing.
|
|
29
|
+
|
|
30
|
+
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**`,
|
|
29
31
|
inputSchema: smokeTestSchema,
|
|
30
32
|
_meta: {
|
|
31
33
|
keywords: ["smoke test", "quick test"],
|
|
@@ -33,7 +33,8 @@ const uiTestSchema = {
|
|
|
33
33
|
output: baseSchema.shape.output,
|
|
34
34
|
outputDir: baseSchema.shape.outputDir,
|
|
35
35
|
force: baseSchema.shape.force,
|
|
36
|
-
|
|
36
|
+
codeReuse: codeRefactoringSchema.shape.codeReuse,
|
|
37
|
+
modularizeCode: codeRefactoringSchema.shape.modularizeCode.default(true),
|
|
37
38
|
};
|
|
38
39
|
export function registerUITestTool(server) {
|
|
39
40
|
server.registerTool(TOOL_NAME, {
|
|
@@ -70,13 +70,18 @@ Output: Detailed RepositoryAnalysis JSON object with all repository characterist
|
|
|
70
70
|
repositoryPath: params.repositoryPath,
|
|
71
71
|
scanDepth: params.scanDepth,
|
|
72
72
|
});
|
|
73
|
-
// Return prompt for LLM to execute
|
|
74
73
|
const analysisPrompt = getRepositoryAnalysisPrompt(params.repositoryPath);
|
|
75
74
|
return {
|
|
76
75
|
content: [
|
|
77
76
|
{
|
|
78
77
|
type: "text",
|
|
79
|
-
text:
|
|
78
|
+
text: `⚠️ CRITICAL INSTRUCTION - READ FIRST:
|
|
79
|
+
- Return the JSON object directly in your response text.
|
|
80
|
+
- DO NOT create or save any files (no repository_analysis.json or .md files).
|
|
81
|
+
- DO NOT use file write tools or save analysis to disk.
|
|
82
|
+
- Output the complete analysis JSON inline in your response.
|
|
83
|
+
|
|
84
|
+
# Repository Analysis Request
|
|
80
85
|
|
|
81
86
|
Please analyze the repository at: \`${params.repositoryPath}\`
|
|
82
87
|
|
|
@@ -38,7 +38,7 @@ The scoring algorithm considers:
|
|
|
38
38
|
- Available artifacts (OpenAPI specs, Playwright recordings)
|
|
39
39
|
- Security requirements (authentication, sensitive data)
|
|
40
40
|
|
|
41
|
-
Example usage:
|
|
41
|
+
Example usage (direct JSON):
|
|
42
42
|
\`\`\`
|
|
43
43
|
{
|
|
44
44
|
"analysisReport": "<RepositoryAnalysis JSON from skyramp_analyze_repository>",
|
|
@@ -50,7 +50,10 @@ Example usage:
|
|
|
50
50
|
\`\`\`
|
|
51
51
|
|
|
52
52
|
**CRITICAL RULES**:
|
|
53
|
-
-
|
|
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.
|
|
54
57
|
|
|
55
58
|
Output: TestMappingResult JSON with priority scores, feasibility, and reasoning for each test type.`,
|
|
56
59
|
inputSchema: mapTestsSchema.shape,
|
|
@@ -140,7 +143,13 @@ Output: TestMappingResult JSON with priority scores, feasibility, and reasoning
|
|
|
140
143
|
summary: { highPriority, mediumPriority, lowPriority },
|
|
141
144
|
};
|
|
142
145
|
// Format output
|
|
143
|
-
const 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
|
|
144
153
|
|
|
145
154
|
## Summary
|
|
146
155
|
- **High Priority**: ${mapping.summary.highPriority.join(", ") || "None"}
|
|
@@ -107,6 +107,7 @@ ${prompt}
|
|
|
107
107
|
2. Include guidance for missing artifacts with specific Skyramp tool commands
|
|
108
108
|
3. Prioritize quick wins (tests with all artifacts available)
|
|
109
109
|
4. Use actual endpoint paths and file paths from the repository analysis
|
|
110
|
+
5. CRITICAL: Return the recommendations in text format, not JSON.
|
|
110
111
|
|
|
111
112
|
PRE-REQUISITES:
|
|
112
113
|
STEP 1: Call skyramp_analyze_repository to get repository analysis
|
|
@@ -117,6 +118,7 @@ STEP 2: Call skyramp_map_tests to get test priority scores and repository analys
|
|
|
117
118
|
- DO NOT SHOW ANY PRIORITY BREAKDOWN IN THE OUTPUT.
|
|
118
119
|
- DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
|
|
119
120
|
- DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
|
|
121
|
+
- DO NOT CREATE OR SAVE FILES - output everything in your response text.
|
|
120
122
|
|
|
121
123
|
After analyzing the data above, return the complete JSON response following the structure defined in the prompt.
|
|
122
124
|
**CRITICAL:** At the end of the tool execution, MUST display the below message:
|
package/build/types/TestTypes.js
CHANGED
|
@@ -12,7 +12,13 @@ export var TestType;
|
|
|
12
12
|
export const languageSchema = z.object({
|
|
13
13
|
language: z
|
|
14
14
|
.string()
|
|
15
|
-
.
|
|
15
|
+
.refine((val) => {
|
|
16
|
+
const validLanguages = ["python", "typescript", "javascript", "java"];
|
|
17
|
+
return validLanguages.includes(val.toLowerCase());
|
|
18
|
+
}, {
|
|
19
|
+
message: "Language must be one of: python, typescript, javascript, java",
|
|
20
|
+
})
|
|
21
|
+
.describe("Programming language for the generated test (default: python). Must be one of: python, typescript, javascript, java"),
|
|
16
22
|
framework: z
|
|
17
23
|
.string()
|
|
18
24
|
.describe("Testing framework to use (e.g., pytest for python, playwright for javascript and typescript, junit for java, etc.)"),
|
|
@@ -55,7 +61,15 @@ export const baseSchema = z.object({
|
|
|
55
61
|
output: z
|
|
56
62
|
.string()
|
|
57
63
|
.optional()
|
|
58
|
-
.
|
|
64
|
+
.refine((val) => {
|
|
65
|
+
if (!val || val === "")
|
|
66
|
+
return true; // Allow empty string or undefined
|
|
67
|
+
const validExtensions = [".py", ".ts", ".js", ".java"];
|
|
68
|
+
return validExtensions.some((ext) => val.endsWith(ext));
|
|
69
|
+
}, {
|
|
70
|
+
message: "Output file must have one of these extensions: .py, .ts, .js, .java",
|
|
71
|
+
})
|
|
72
|
+
.describe("Name of the output test file. Must have one of these extensions: .py, .ts, .js, .java. Default value is empty string. If not provided, Skyramp will generate a default name for the test file."),
|
|
59
73
|
outputDir: z
|
|
60
74
|
.string()
|
|
61
75
|
.describe("MUST be absolute path to the directory where test files will be generated. If not provided, the CURRENT WORKING DIRECTORY will be used WITHOUT ANY SUBDIRECTORIES"),
|
|
@@ -104,7 +118,7 @@ export const baseTestSchema = {
|
|
|
104
118
|
apiSchema: z
|
|
105
119
|
.string()
|
|
106
120
|
.default("")
|
|
107
|
-
.describe("MUST be absolute path(/path/to/openapi.json) to the OpenAPI/Swagger schema file or a URL to the OpenAPI/Swagger schema file(e.g. https://demoshop.skyramp.dev/openapi.json). DO NOT TRY TO ASSUME THE OPENAPI SCHEMA IF NOT PROVIDED"),
|
|
121
|
+
.describe("MUST be absolute path(/path/to/openapi.json) to the OpenAPI/Swagger schema file or a URL to the OpenAPI/Swagger schema file(e.g. https://demoshop.skyramp.dev/openapi.json). DO NOT TRY TO ASSUME THE OPENAPI SCHEMA IF NOT PROVIDED. NOTE TO AI ASSISTANTS: You do not need to read the contents of this file - simply pass the file path as the backend will read and process it."),
|
|
108
122
|
pathParams: z
|
|
109
123
|
.string()
|
|
110
124
|
.default("")
|
package/build/utils/logger.js
CHANGED
|
@@ -15,8 +15,12 @@ export class McpLogger {
|
|
|
15
15
|
message,
|
|
16
16
|
...(data && { data }),
|
|
17
17
|
};
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
try {
|
|
19
|
+
console.error(JSON.stringify(logEntry));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// silently ignore
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
debug(message, data) {
|
|
22
26
|
this.sendLogMessage("debug", message, data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
46
|
-
"@skyramp/skyramp": "^1.
|
|
46
|
+
"@skyramp/skyramp": "^1.3.0",
|
|
47
47
|
"@playwright/test": "^1.55.0",
|
|
48
48
|
"dockerode": "^4.0.6",
|
|
49
49
|
"fast-glob": "^3.3.3",
|