@skyramp/mcp 0.0.1
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/README.md +163 -0
- package/build/index.js +55 -0
- package/build/prompts/startTraceCollectionPrompts.js +54 -0
- package/build/prompts/stopTraceCollectionPrompts.js +113 -0
- package/build/prompts/testGenerationPrompt.js +374 -0
- package/build/services/TestGenerationService.js +138 -0
- package/build/tools/executeSkyrampTestTool.js +107 -0
- package/build/tools/generateContractRestTool.js +34 -0
- package/build/tools/generateE2ERestTool.js +34 -0
- package/build/tools/generateFuzzRestTool.js +32 -0
- package/build/tools/generateIntegrationRestTool.js +29 -0
- package/build/tools/generateLoadRestTool.js +61 -0
- package/build/tools/generateSmokeRestTool.js +23 -0
- package/build/tools/generateUIRestTool.js +37 -0
- package/build/tools/startTraceCollectionTool.js +79 -0
- package/build/tools/stopTraceCollectionTool.js +86 -0
- package/build/types/TestTypes.js +101 -0
- package/build/utils/analyze-openapi.js +25 -0
- package/build/utils/logger.js +38 -0
- package/build/utils/proxy-terminal.js +439 -0
- package/build/utils/utils.js +118 -0
- package/build/utils/utils.test.js +32 -0
- package/package.json +61 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { baseSchema, baseTraceSchema } from "../types/TestTypes.js";
|
|
3
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
|
+
const e2eTestSchema = {
|
|
5
|
+
...baseSchema.shape,
|
|
6
|
+
...baseTraceSchema.shape,
|
|
7
|
+
playwrightInput: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("MUST be absolute path to the playwright input file like /path/to/playwright-ui-test.zip and MUST be a zip file"),
|
|
10
|
+
};
|
|
11
|
+
export class E2ETestService extends TestGenerationService {
|
|
12
|
+
getTestType() {
|
|
13
|
+
return "e2e";
|
|
14
|
+
}
|
|
15
|
+
buildGenerationOptions(params) {
|
|
16
|
+
return {
|
|
17
|
+
...super.buildBaseGenerationOptions(params),
|
|
18
|
+
responseData: params.responseData,
|
|
19
|
+
playwrightInput: params.playwrightInput,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function registerEndtoEndTestTool(server) {
|
|
24
|
+
server.registerTool("skyramp_e2e_test_generation", {
|
|
25
|
+
description: "Generate an E2E test",
|
|
26
|
+
inputSchema: e2eTestSchema,
|
|
27
|
+
annotations: {
|
|
28
|
+
keywords: ["e2e test", "end-to-end test"],
|
|
29
|
+
},
|
|
30
|
+
}, async (params) => {
|
|
31
|
+
const service = new E2ETestService();
|
|
32
|
+
return await service.generateTest(params);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { baseTestSchema } from "../types/TestTypes.js";
|
|
2
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const fuzzTestSchema = {
|
|
5
|
+
...baseTestSchema,
|
|
6
|
+
responseData: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Sample response body data, provided either as an inline JSON/YAML string or as an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)"),
|
|
9
|
+
};
|
|
10
|
+
export class FuzzTestService extends TestGenerationService {
|
|
11
|
+
getTestType() {
|
|
12
|
+
return "fuzz";
|
|
13
|
+
}
|
|
14
|
+
buildGenerationOptions(params) {
|
|
15
|
+
return {
|
|
16
|
+
...super.buildBaseGenerationOptions(params),
|
|
17
|
+
responseData: params.responseData,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function registerFuzzTestTool(server) {
|
|
22
|
+
server.registerTool("skyramp_fuzz_test_generation", {
|
|
23
|
+
description: "Generate a fuzz test",
|
|
24
|
+
inputSchema: fuzzTestSchema,
|
|
25
|
+
annotations: {
|
|
26
|
+
keywords: ["negative test", "random test"],
|
|
27
|
+
},
|
|
28
|
+
}, async (params) => {
|
|
29
|
+
const service = new FuzzTestService();
|
|
30
|
+
return await service.generateTest(params);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { baseTestSchema, baseTraceSchema, } from "../types/TestTypes.js";
|
|
2
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
3
|
+
const integrationTestSchema = {
|
|
4
|
+
...baseTestSchema,
|
|
5
|
+
trace: baseTraceSchema.shape.trace.optional(),
|
|
6
|
+
include: baseTraceSchema.shape.include.optional(),
|
|
7
|
+
exclude: baseTraceSchema.shape.exclude.optional(),
|
|
8
|
+
};
|
|
9
|
+
export class IntegrationTestService extends TestGenerationService {
|
|
10
|
+
getTestType() {
|
|
11
|
+
return "integration";
|
|
12
|
+
}
|
|
13
|
+
buildGenerationOptions(params) {
|
|
14
|
+
return {
|
|
15
|
+
...super.buildBaseGenerationOptions(params),
|
|
16
|
+
responseData: params.responseData,
|
|
17
|
+
playwrightInput: params.playwrightInput,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function registerIntegrationTestTool(server) {
|
|
22
|
+
server.registerTool("skyramp_integration_test_generation", {
|
|
23
|
+
description: "Generate an integration test",
|
|
24
|
+
inputSchema: integrationTestSchema,
|
|
25
|
+
}, async (params) => {
|
|
26
|
+
const service = new IntegrationTestService();
|
|
27
|
+
return await service.generateTest(params);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { baseTestSchema, baseTraceSchema, } from "../types/TestTypes.js";
|
|
3
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
|
+
const loadTestSchema = {
|
|
5
|
+
...baseTestSchema,
|
|
6
|
+
loadCount: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Total number of requests to send during the load test"),
|
|
10
|
+
loadDuration: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Duration of the load test in seconds (default: 5)"),
|
|
14
|
+
loadNumThreads: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Number of concurrent threads/users for the load test (default: 1)"),
|
|
18
|
+
loadRampupDuration: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Time in seconds to gradually increase load to target level"),
|
|
22
|
+
loadRampupInterval: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Interval in seconds between ramping up threads"),
|
|
26
|
+
loadTargetRPS: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Target requests per second for the load test"),
|
|
30
|
+
trace: baseTraceSchema.shape.trace.optional(),
|
|
31
|
+
include: baseTraceSchema.shape.include.optional(),
|
|
32
|
+
exclude: baseTraceSchema.shape.exclude.optional(),
|
|
33
|
+
};
|
|
34
|
+
export class LoadTestService extends TestGenerationService {
|
|
35
|
+
getTestType() {
|
|
36
|
+
return "load";
|
|
37
|
+
}
|
|
38
|
+
buildGenerationOptions(params) {
|
|
39
|
+
return {
|
|
40
|
+
...super.buildBaseGenerationOptions(params),
|
|
41
|
+
loadCount: params.loadCount,
|
|
42
|
+
loadDuration: params.loadDuration,
|
|
43
|
+
loadNumThreads: params.loadNumThreads,
|
|
44
|
+
loadRampupDuration: params.loadRampupDuration,
|
|
45
|
+
loadRampupInterval: params.loadRampupInterval,
|
|
46
|
+
loadTargetRPS: params.loadTargetRPS,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function registerLoadTestTool(server) {
|
|
51
|
+
server.registerTool("skyramp_load_test_generation", {
|
|
52
|
+
description: "Generate a load test",
|
|
53
|
+
inputSchema: loadTestSchema,
|
|
54
|
+
annotations: {
|
|
55
|
+
keywords: ["load test", "performance test"],
|
|
56
|
+
},
|
|
57
|
+
}, async (params) => {
|
|
58
|
+
const service = new LoadTestService();
|
|
59
|
+
return await service.generateTest(params);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { baseTestSchema } from "../types/TestTypes.js";
|
|
2
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
3
|
+
// Concrete implementations for each test type
|
|
4
|
+
export class SmokeTestService extends TestGenerationService {
|
|
5
|
+
getTestType() {
|
|
6
|
+
return "smoke";
|
|
7
|
+
}
|
|
8
|
+
buildGenerationOptions(params) {
|
|
9
|
+
return super.buildBaseGenerationOptions(params);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function registerSmokeTestTool(server) {
|
|
13
|
+
server.registerTool("skyramp_smoke_test_generation", {
|
|
14
|
+
description: "Generate a smoke test",
|
|
15
|
+
inputSchema: baseTestSchema,
|
|
16
|
+
annotations: {
|
|
17
|
+
keywords: ["smoke test", "quick test"],
|
|
18
|
+
},
|
|
19
|
+
}, async (params) => {
|
|
20
|
+
const service = new SmokeTestService();
|
|
21
|
+
return await service.generateTest(params);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { baseSchema } from "../types/TestTypes.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
|
+
export class UITestService extends TestGenerationService {
|
|
5
|
+
getTestType() {
|
|
6
|
+
return "ui";
|
|
7
|
+
}
|
|
8
|
+
buildGenerationOptions(params) {
|
|
9
|
+
const options = this.buildBaseGenerationOptions(params);
|
|
10
|
+
return {
|
|
11
|
+
...options,
|
|
12
|
+
playwrightInput: params.playwrightInput,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async handleApiAnalysis(params, generateOptions) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Only include the original params in the schema
|
|
20
|
+
const uiTestSchema = {
|
|
21
|
+
...baseSchema.shape,
|
|
22
|
+
playwrightInput: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("MUST be absolute path to the playwright input file like /path/to/playwright-ui-test.zip and MUST be a zip file"),
|
|
25
|
+
};
|
|
26
|
+
export function registerUITestTool(server) {
|
|
27
|
+
server.registerTool("skyramp_ui_test_generation", {
|
|
28
|
+
description: "Generate an UI test",
|
|
29
|
+
inputSchema: uiTestSchema,
|
|
30
|
+
annotations: {
|
|
31
|
+
keywords: ["ui test", "playwright"],
|
|
32
|
+
},
|
|
33
|
+
}, async (params) => {
|
|
34
|
+
const service = new UITestService();
|
|
35
|
+
return await service.generateTest(params);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SkyrampClient } from "@skyramp/skyramp";
|
|
3
|
+
import openProxyTerminalTracked from "../utils/proxy-terminal.js";
|
|
4
|
+
import { TELEMETRY_entrypoint_FIELD_NAME } from "../utils/utils.js";
|
|
5
|
+
export function registerTraceTool(server) {
|
|
6
|
+
server.registerTool("skyramp_start_trace_generation", {
|
|
7
|
+
description: "Start trace collection",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
playwright: z
|
|
10
|
+
.boolean()
|
|
11
|
+
.describe("Whether to enable Playwright for trace collection. Set to true for UI interactions, false for API-only tracing")
|
|
12
|
+
.default(true),
|
|
13
|
+
runtime: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("Runtime environment for trace collection (docker, kubernetes, etc.). Defaults to docker if not specified")
|
|
16
|
+
.default("docker"),
|
|
17
|
+
include: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("List of service names or patterns to include in trace collection"),
|
|
21
|
+
exclude: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("List of service names or patterns to exclude from trace collection"),
|
|
25
|
+
noProxy: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("List of hosts or patterns that should bypass proxy during tracing"),
|
|
29
|
+
dockerNetwork: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Docker network name to use for containerized trace collection"),
|
|
33
|
+
dockerWorkerPort: z
|
|
34
|
+
.number()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Port number for the Docker worker service during trace collection"),
|
|
37
|
+
},
|
|
38
|
+
annotations: {
|
|
39
|
+
keywords: ["start trace", "trace collection", "trace generation"],
|
|
40
|
+
},
|
|
41
|
+
}, async (params) => {
|
|
42
|
+
const client = new SkyrampClient();
|
|
43
|
+
const generateOptions = {
|
|
44
|
+
testType: "trace",
|
|
45
|
+
runtime: params.runtime ?? "docker",
|
|
46
|
+
dockerNetwork: params.dockerNetwork ?? "skyramp",
|
|
47
|
+
dockerWorkerPort: (params.dockerWorkerPort ?? 35142).toString(),
|
|
48
|
+
generateInclude: params.include,
|
|
49
|
+
generateExclude: params.exclude,
|
|
50
|
+
generateNoProxy: params.noProxy,
|
|
51
|
+
unblock: true,
|
|
52
|
+
playwright: params.playwright,
|
|
53
|
+
entrypoint: TELEMETRY_entrypoint_FIELD_NAME,
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
const result = await client.generateRestTest(generateOptions);
|
|
57
|
+
await openProxyTerminalTracked();
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Trace collection started: ${result}. Please let me know when you are ready to stop the trace collection.`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Trace generation failed: ${error.message}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { SkyrampClient } from "@skyramp/skyramp";
|
|
2
|
+
import { closeProxyTerminal } from "../utils/proxy-terminal.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { PLAYWRIGHT_OUTPUT_FILE_FIELD_NAME, TELEMETRY_entrypoint_FIELD_NAME, TRACE_OUTPUT_FILE_FIELD_NAME, validatePath, } from "../utils/utils.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
export function registerTraceStopTool(server) {
|
|
7
|
+
server.registerTool("skyramp_stop_trace_generation", {
|
|
8
|
+
description: "Stop trace collection",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
traceOutputFile: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("MUST be absolute path to save the trace output file (e.g., /path/to/skyramp-traces.json). This file will contain the collected trace data"),
|
|
13
|
+
playwright: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.describe("Whether Playwright was used for trace collection. Must match the setting used when starting trace collection"),
|
|
16
|
+
playwrightOutput: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("MUST be absolute path to save the Playwright output file (e.g., /path/to/playwright.zip). Only used when playwright is true"),
|
|
19
|
+
},
|
|
20
|
+
annotations: {
|
|
21
|
+
keywords: ["stop trace", "collect generated trace"],
|
|
22
|
+
},
|
|
23
|
+
}, async (params) => {
|
|
24
|
+
const client = new SkyrampClient();
|
|
25
|
+
try {
|
|
26
|
+
const stopTraceOptions = {
|
|
27
|
+
output: params.traceOutputFile ?? "skyramp-traces.json",
|
|
28
|
+
workerContainerName: "skyramp-trace-worker",
|
|
29
|
+
playwright: params.playwright ?? false,
|
|
30
|
+
playwrightOutput: params.playwrightOutput ?? "playwright.zip",
|
|
31
|
+
entrypoint: TELEMETRY_entrypoint_FIELD_NAME,
|
|
32
|
+
};
|
|
33
|
+
let errList = {
|
|
34
|
+
content: [],
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
if (stopTraceOptions.output == "" &&
|
|
38
|
+
stopTraceOptions.playwrightOutput == "") {
|
|
39
|
+
errList.content.push({
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "Error: Either trace output file or playwright output file is required.",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
logger.debug("Trace collection stop options", {
|
|
45
|
+
playwright: stopTraceOptions.playwright,
|
|
46
|
+
});
|
|
47
|
+
if (!stopTraceOptions.playwright) {
|
|
48
|
+
logger.info("Playwright disabled for trace collection");
|
|
49
|
+
stopTraceOptions.playwrightOutput = "";
|
|
50
|
+
}
|
|
51
|
+
let err = validatePath(stopTraceOptions.output, TRACE_OUTPUT_FILE_FIELD_NAME);
|
|
52
|
+
if (err && err.content) {
|
|
53
|
+
errList.content.push(err.content[0]);
|
|
54
|
+
}
|
|
55
|
+
err = validatePath(stopTraceOptions.playwrightOutput, PLAYWRIGHT_OUTPUT_FILE_FIELD_NAME);
|
|
56
|
+
if (err && err.content) {
|
|
57
|
+
errList.content.push(err.content[0]);
|
|
58
|
+
}
|
|
59
|
+
//if error is not empty, return error
|
|
60
|
+
if (errList.content.length > 0) {
|
|
61
|
+
return errList;
|
|
62
|
+
}
|
|
63
|
+
await closeProxyTerminal();
|
|
64
|
+
const result = await client.traceCollect(stopTraceOptions);
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: `Trace collection is stopped: ${result}. Trace is generated to given output file`,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: `Trace generation result: ${error.message}`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const baseSchema = z.object({
|
|
3
|
+
language: z
|
|
4
|
+
.string()
|
|
5
|
+
.optional()
|
|
6
|
+
.describe("Programming language for the generated test (default: python)"),
|
|
7
|
+
framework: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Testing framework to use (e.g., pytest for python, playwright for javascript, junit for java, etc.)"),
|
|
11
|
+
output: z.string().optional().describe("Name of the output test file"),
|
|
12
|
+
outputDir: z
|
|
13
|
+
.string()
|
|
14
|
+
.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"),
|
|
15
|
+
force: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.describe("Whether to overwrite existing files in the output directory"),
|
|
18
|
+
deployDashboard: z
|
|
19
|
+
.boolean()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Whether to deploy the Skyramp dashboard for test monitoring"),
|
|
22
|
+
runtime: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Runtime environment (docker, kubernetes, etc.)"),
|
|
26
|
+
dockerNetwork: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Docker network name to use for containerized testing"),
|
|
30
|
+
dockerWorkerPort: z
|
|
31
|
+
.number()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Port number for the Docker worker service"),
|
|
34
|
+
k8sNamespace: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Kubernetes namespace for deployment"),
|
|
38
|
+
k8sConfig: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Path to Kubernetes configuration file"),
|
|
42
|
+
k8sContext: z.string().optional().describe("Kubernetes context to use"),
|
|
43
|
+
authHeader: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Authorization header value for authenticated requests"),
|
|
47
|
+
insecure: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Whether to allow insecure SSL connections for HTTPS endpoints"),
|
|
51
|
+
});
|
|
52
|
+
export const baseTraceSchema = z.object({
|
|
53
|
+
...baseSchema.shape,
|
|
54
|
+
trace: z
|
|
55
|
+
.string()
|
|
56
|
+
.describe("MUST be absolute path to trace file for test generation like /path/to/trace.json and MUST be a json file"),
|
|
57
|
+
include: z
|
|
58
|
+
.array(z.string())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("List of endpoints or patterns to include in test generation"),
|
|
61
|
+
exclude: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("List of endpoints or patterns to exclude from test generation"),
|
|
65
|
+
});
|
|
66
|
+
// Base schema that all test tools share
|
|
67
|
+
export const baseTestSchema = {
|
|
68
|
+
...baseSchema.shape,
|
|
69
|
+
endpointURL: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("The endpoint URL to test (e.g., https://api.example.com/v1/products)"),
|
|
73
|
+
method: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("HTTP method to use (GET, POST, PUT, DELETE, etc.)"),
|
|
77
|
+
apiSchema: z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("MUST be absolute path to the OpenAPI/Swagger schema file"),
|
|
81
|
+
pathParams: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("MUST be string of comma separated values like 'id=1,name=John' for URL path parameters"),
|
|
85
|
+
queryParams: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("MUST be string of comma separated values like 'id=1,name=John' for URL query parameters"),
|
|
89
|
+
formParams: z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("MUST be string of comma separated values like 'id=1,name=John' for form data parameters"),
|
|
93
|
+
requestData: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Sample request body data, provided either as an inline JSON/YAML string or as an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)."),
|
|
97
|
+
responseStatusCode: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe("Expected HTTP response status code (e.g., '200', '201', '404')"),
|
|
101
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SkyrampClient } from "@skyramp/skyramp";
|
|
2
|
+
import { IsValidKeyValueList } from "./utils.js";
|
|
3
|
+
// Analyze OpenAPI to check for required path params
|
|
4
|
+
export async function analyzeOpenAPIWithGivenEndpoint(apiSchemaInput, uriInput, pathParamsInput) {
|
|
5
|
+
const client = new SkyrampClient();
|
|
6
|
+
const analyzeOpenapiOptions = {
|
|
7
|
+
apiSchema: apiSchemaInput,
|
|
8
|
+
uri: uriInput,
|
|
9
|
+
};
|
|
10
|
+
const pathParamRequired = await client.analyzeOpenapi(analyzeOpenapiOptions);
|
|
11
|
+
if (IsValidKeyValueList(pathParamRequired)) {
|
|
12
|
+
const requiredPathParams = pathParamRequired
|
|
13
|
+
? pathParamRequired.split(",").map((p) => p.trim())
|
|
14
|
+
: [];
|
|
15
|
+
let providedPathParams = pathParamsInput
|
|
16
|
+
? pathParamsInput.split(",").map((param) => param.split("=")[0].trim())
|
|
17
|
+
: [];
|
|
18
|
+
// Find missing path parameters by comparing keys
|
|
19
|
+
let missingPathParams = requiredPathParams.filter((param) => !providedPathParams.includes(param));
|
|
20
|
+
if (missingPathParams.length > 0) {
|
|
21
|
+
return `${missingPathParams.join(", ")}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class McpLogger {
|
|
2
|
+
loggerName;
|
|
3
|
+
constructor(loggerName = "skyramp-mcp") {
|
|
4
|
+
this.loggerName = loggerName;
|
|
5
|
+
}
|
|
6
|
+
sendLogMessage(level, message, data) {
|
|
7
|
+
// MCP servers should use console.error for logging to stderr per MCP spec
|
|
8
|
+
// This ensures logs are properly captured by MCP clients and don't interfere
|
|
9
|
+
// with the JSON-RPC protocol communication on stdout
|
|
10
|
+
const timestamp = new Date().toISOString();
|
|
11
|
+
const logEntry = {
|
|
12
|
+
timestamp,
|
|
13
|
+
level: level.toUpperCase(),
|
|
14
|
+
logger: this.loggerName,
|
|
15
|
+
message,
|
|
16
|
+
...(data && { data }),
|
|
17
|
+
};
|
|
18
|
+
// Use console.error for structured logging (this goes to stderr)
|
|
19
|
+
console.error(JSON.stringify(logEntry));
|
|
20
|
+
}
|
|
21
|
+
debug(message, data) {
|
|
22
|
+
this.sendLogMessage("debug", message, data);
|
|
23
|
+
}
|
|
24
|
+
info(message, data) {
|
|
25
|
+
this.sendLogMessage("info", message, data);
|
|
26
|
+
}
|
|
27
|
+
warning(message, data) {
|
|
28
|
+
this.sendLogMessage("warning", message, data);
|
|
29
|
+
}
|
|
30
|
+
error(message, data) {
|
|
31
|
+
this.sendLogMessage("error", message, data);
|
|
32
|
+
}
|
|
33
|
+
critical(message, data) {
|
|
34
|
+
this.sendLogMessage("critical", message, data);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Export singleton instance
|
|
38
|
+
export const logger = new McpLogger();
|