@skyramp/mcp
Version:
Skyramp MCP (Model Context Protocol) Server - AI-powered test generation and execution
195 lines (190 loc) • 7.88 kB
JavaScript
import { SkyrampClient } from "@skyramp/skyramp";
import { analyzeOpenAPIWithGivenEndpoint } from "../utils/analyze-openapi.js";
import { getPathParameterValidationError, OUTPUT_DIR_FIELD_NAME, PATH_PARAMS_FIELD_NAME, QUERY_PARAMS_FIELD_NAME, FORM_PARAMS_FIELD_NAME, validateParams, validatePath, validateRequestData, TELEMETRY_entrypoint_FIELD_NAME, } from "../utils/utils.js";
import { getLanguageSteps } from "../utils/language-helper.js";
import { logger } from "../utils/logger.js";
export class TestGenerationService {
client;
constructor() {
this.client = new SkyrampClient();
}
async generateTest(params) {
try {
// Log prompt parameter using reusable utility
logger.info("Generating test", {
prompt: params.prompt,
testType: this.getTestType(),
language: params.language,
outputDir: params.outputDir,
});
const validationResult = this.validateInputs(params);
if (validationResult.isError) {
return validationResult;
}
const generateOptions = this.buildGenerationOptions(params);
const apiAnalysisResult = await this.handleApiAnalysis(params, generateOptions);
if (apiAnalysisResult) {
return apiAnalysisResult;
}
const result = await this.executeGeneration(generateOptions);
const testType = this.getTestType();
const languageSteps = getLanguageSteps({
language: params.language || "",
testType: testType,
framework: params.framework,
});
// Orchestrate code optimization workflow based on flags
let postGenerationMessage = "";
if (params.codeReuse) {
// Only code reuse
postGenerationMessage += `
Test generated successfully!
⏭️**CRITICAL NEXT STEP**: Running code reuse analysis with skyramp_reuse_code tool.
`;
}
else if (params.modularizeCode) {
// Only modularization
postGenerationMessage += `
Test generated successfully!
⏭️**CRITICAL NEXT STEP**: Running modularization with skyramp_modularization tool.
`;
}
return {
content: [
{
type: "text",
text: `${postGenerationMessage}${result}
${languageSteps}`,
},
],
isError: false,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: errorMessage,
},
],
isError: true,
};
}
}
validateInputs(params) {
const errList = { content: [], isError: true };
const pathError = validatePath(params.outputDir, OUTPUT_DIR_FIELD_NAME);
if (pathError?.content)
errList.content.push(pathError.content[0]);
[
validateParams(params.pathParams ?? "", PATH_PARAMS_FIELD_NAME),
validateParams(params.queryParams ?? "", QUERY_PARAMS_FIELD_NAME),
validateParams(params.formParams ?? "", FORM_PARAMS_FIELD_NAME),
].forEach((error) => {
if (error?.content)
errList.content.push(error.content[0]);
});
if (params.requestData &&
validateRequestData(params.requestData) === null) {
errList.content.push({
type: "text",
text: "Error: requestData must be either a valid JSON string or an absolute path to a file.",
});
}
return errList.content.length === 0
? { content: [], isError: false }
: errList;
}
async handleApiAnalysis(params, generateOptions) {
if (params.apiSchema && params.endpointURL && params.method === "") {
try {
const requiredPathParams = await analyzeOpenAPIWithGivenEndpoint(params.apiSchema, params.endpointURL, params.pathParams ?? "");
if (requiredPathParams && requiredPathParams.trim().length > 0) {
return {
content: [
{
type: "text",
text: `${getPathParameterValidationError(requiredPathParams)}
**IMPORTANT: MAKE SURE TO PROVIDE THE PATH PARAMS THAT DO NOT GIVE 404 ERROR FOR GET/DELETE METHODS. IT WILL BE USED AS PATH PARAMS IN THE TEST GENERATION.**`,
},
],
isError: true,
};
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error analyzing OpenAPI schema: ${errorMessage}`,
},
],
isError: true,
};
}
}
return null;
}
async executeGeneration(generateOptions) {
try {
const result = await this.client.generateRestTest(generateOptions);
// Check if the result indicates failure
if (result && result.length > 0) {
const lowerResult = result.toLowerCase();
if (!lowerResult.includes("success")) {
throw new Error(`Test generation failed: ${result}`);
}
}
return `
**Generated Test Details:**
- Test Type: ${this.getTestType()}
- Output Directory: ${generateOptions.outputDir}
- Language: ${generateOptions.language}
- Framework: ${generateOptions.framework}
${result}`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to execute test generation: ${errorMessage}`);
}
}
buildBaseGenerationOptions(params) {
return {
testType: this.getTestType(),
uri: params.endpointURL,
method: params.method,
apiSchema: params.apiSchema ? [params.apiSchema] : [],
language: params.language ?? "python",
framework: params.framework ??
(params.language === "typescript" || params.language === "javascript"
? "playwright"
: ""),
output: params.output,
outputDir: params.outputDir,
force: params.force ?? true,
deployDashboard: params.deployDashboard,
runtime: params.runtime,
dockerNetwork: params.dockerNetwork,
dockerWorkerPort: params.dockerWorkerPort?.toString(),
k8sNamespace: params.k8sNamespace,
k8sConfig: params.k8sConfig,
k8sContext: params.k8sContext,
authHeader: params.authHeader,
requestData: params.requestData,
pathParams: params.pathParams,
queryParams: params.queryParams,
formParams: params.formParams,
responseStatusCode: params.responseStatusCode,
traceFilePath: params.trace,
generateInclude: params.include,
generateExclude: params.exclude,
generateInsecure: params.insecure,
entrypoint: TELEMETRY_entrypoint_FIELD_NAME,
chainingKey: params.chainingKey,
};
}
}