@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 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
- process.stdin.on("end", () => {
131
- logger.info("STDIN closed, parent process disconnected. Exiting...");
132
- process.exit(0);
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
- logger.info("Received SIGTERM, shutting down gracefully...");
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
- Begin analysis now. Return ONLY the JSON object, no other text.
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
- Generate recommendations now. Return ONLY the JSON object, no other text.
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.2.40";
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 === '/' && nextChar === '/') {
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('//') || 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('\n');
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}`, { error });
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
- // Wait for Docker image to be ready
226
- await this.imageReady;
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 the timeout timer if execution completes successfully
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 the timeout timer on any error
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 host (e.g., api.example.com) to be used for the test"),
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
- ...codeRefactoringSchema.shape,
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: `# Repository Analysis Request
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
- - DO NOT CREATE ANY .json or .md file during test mapping.
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 = `# Test Priority Mapping
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:
@@ -12,7 +12,13 @@ export var TestType;
12
12
  export const languageSchema = z.object({
13
13
  language: z
14
14
  .string()
15
- .describe("Programming language for the generated test (default: python)"),
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
- .describe("Name of the output test file. Default value is empty string. If not provided, Skyramp will generate a default name for the test file."),
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("")
@@ -15,8 +15,12 @@ export class McpLogger {
15
15
  message,
16
16
  ...(data && { data }),
17
17
  };
18
- // Use console.error for structured logging (this goes to stderr)
19
- console.error(JSON.stringify(logEntry));
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.46",
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.2.40",
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",