UNPKG

@skyramp/mcp

Version:

Skyramp MCP (Model Context Protocol) Server - AI-powered test generation and execution

195 lines (190 loc) 7.88 kB
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, }; } }