@skyramp/mcp
Version:
Skyramp MCP (Model Context Protocol) Server - AI-powered test generation and execution
170 lines (169 loc) • 7.51 kB
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerStartTraceCollectionPrompt } from "./prompts/startTraceCollectionPrompts.js";
import { registerTestHealthPrompt } from "./prompts/testHealthPrompt.js";
import { registerTraceTool } from "./tools/trace/startTraceCollectionTool.js";
import { registerTraceStopTool } from "./tools/trace/stopTraceCollectionTool.js";
import { registerExecuteSkyrampTestTool } from "./tools/executeSkyrampTestTool.js";
import { registerTestGenerationPrompt } from "./prompts/testGenerationPrompt.js";
import { logger } from "./utils/logger.js";
import { registerUITestTool } from "./tools/generate-tests/generateUIRestTool.js";
import { registerSmokeTestTool } from "./tools/generate-tests/generateSmokeRestTool.js";
import { registerFuzzTestTool } from "./tools/generate-tests/generateFuzzRestTool.js";
import { registerContractTestTool } from "./tools/generate-tests/generateContractRestTool.js";
import { registerLoadTestTool } from "./tools/generate-tests/generateLoadRestTool.js";
import { registerIntegrationTestTool } from "./tools/generate-tests/generateIntegrationRestTool.js";
import { registerE2ETestTool } from "./tools/generate-tests/generateE2ERestTool.js";
import { registerLoginTool } from "./tools/auth/loginTool.js";
import { registerLogoutTool } from "./tools/auth/logoutTool.js";
import { registerFixErrorTool } from "./tools/fixErrorTool.js";
import { registerAnalyzeRepositoryTool } from "./tools/test-recommendation/analyzeRepositoryTool.js";
import { registerMapTestsTool } from "./tools/test-recommendation/mapTestsTool.js";
import { registerRecommendTestsTool } from "./tools/test-recommendation/recommendTestsTool.js";
import { registerModularizationTool } from "./tools/code-refactor/modularizationTool.js";
import { registerCodeReuseTool } from "./tools/code-refactor/codeReuseTool.js";
import { registerScenarioTestTool } from "./tools/generate-tests/generateScenarioRestTool.js";
import { registerDiscoverTestsTool } from "./tools/test-maintenance/discoverTestsTool.js";
import { registerAnalyzeTestDriftTool } from "./tools/test-maintenance/analyzeTestDriftTool.js";
import { registerExecuteBatchTestsTool } from "./tools/test-maintenance/executeBatchTestsTool.js";
import { registerCalculateHealthScoresTool } from "./tools/test-maintenance/calculateHealthScoresTool.js";
import { registerActionsTool } from "./tools/test-maintenance/actionsTool.js";
import { registerStateCleanupTool } from "./tools/test-maintenance/stateCleanupTool.js";
import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
import { registerInitTestbotTool } from "./tools/initTestbotTool.js";
import { AnalyticsService } from "./services/AnalyticsService.js";
const server = new McpServer({
name: "Skyramp MCP Server",
version: "1.0.0",
}, {
capabilities: {
tools: {
listChanged: true,
},
prompts: {
listChanged: true,
},
},
});
// Register prompts
logger.info("Starting prompt registration process");
const prompts = [
registerTestGenerationPrompt,
registerStartTraceCollectionPrompt,
registerTestHealthPrompt,
];
if (process.env.SKYRAMP_FEATURE_TESTBOT === "1") {
prompts.push(registerTestbotPrompt);
logger.info("TestBot prompt enabled via SKYRAMP_ENABLE_TESTBOT");
}
prompts.forEach((registerPrompt) => registerPrompt(server));
logger.info("All prompts registered successfully");
// Register test generation tools
const testGenerationTools = [
registerSmokeTestTool,
registerFuzzTestTool,
registerContractTestTool,
registerLoadTestTool,
registerIntegrationTestTool,
registerE2ETestTool,
registerUITestTool,
registerScenarioTestTool,
];
testGenerationTools.forEach((registerTool) => registerTool(server));
// Register modularization and code quality tools
const codeQualityTools = [
registerModularizationTool,
registerFixErrorTool,
registerCodeReuseTool,
];
codeQualityTools.forEach((registerTool) => registerTool(server));
// Register test recommendation tools
registerAnalyzeRepositoryTool(server);
registerMapTestsTool(server);
registerRecommendTestsTool(server);
// Register test maintenance tools
registerDiscoverTestsTool(server);
registerAnalyzeTestDriftTool(server);
registerExecuteBatchTestsTool(server);
registerCalculateHealthScoresTool(server);
registerActionsTool(server);
registerStateCleanupTool(server);
// Register other Skyramp tools
const infrastructureTools = [
registerLoginTool,
registerLogoutTool,
registerExecuteSkyrampTestTool,
registerTraceTool,
registerTraceStopTool,
];
if (process.env.SKYRAMP_FEATURE_TESTBOT === "1") {
infrastructureTools.push(registerInitTestbotTool);
logger.info("TestBot init tool enabled via SKYRAMP_FEATURE_TESTBOT");
}
infrastructureTools.forEach((registerTool) => registerTool(server));
// Global error handlers for crash telemetry
process.on("uncaughtException", async (error) => {
// Don't send analytics for EPIPE errors
const isEpipeError = "code" in error && error.code === "EPIPE";
if (isEpipeError) {
process.exit(0);
}
logger.critical("Uncaught exception - MCP server crashing", {
error: error.message,
stack: error.stack,
});
try {
await AnalyticsService.pushServerCrashEvent("uncaughtException", error.message, error.stack);
}
catch (telemetryError) {
logger.error("Failed to send crash telemetry", { telemetryError });
}
process.exit(1);
});
// Start MCP server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
logger.info("MCP Server started successfully");
// Listen for stdin closure (parent process disconnected)
// Using multiple events for robustness across different disconnection scenarios
let isShuttingDown = false;
const handleParentDisconnect = (event) => {
if (isShuttingDown)
return;
isShuttingDown = true;
logger.info(`Parent process disconnected (${event}). Cleaning up and exiting...`);
server.close().finally(() => {
process.exit(0);
});
};
process.stdin.on("end", () => handleParentDisconnect("stdin end"));
process.stdin.on("close", () => handleParentDisconnect("stdin close"));
process.stdin.on("error", (err) => {
logger.error("STDIN error, parent likely disconnected", { error: err });
handleParentDisconnect("stdin error");
});
// Handle process termination signals
process.on("SIGTERM", () => handleParentDisconnect("SIGTERM"));
process.on("SIGINT", () => handleParentDisconnect("SIGINT"));
}
main().catch(async (error) => {
// Don't send analytics for EPIPE errors - broken pipe means we can't write anyway
// Node.js system errors have a 'code' property with error codes like 'EPIPE'
const isEpipeError = "code" in error && error.code === "EPIPE";
if (isEpipeError) {
process.exit(0);
}
logger.critical("Fatal error in main()", {
error: error.message,
stack: error.stack,
});
try {
await AnalyticsService.pushServerCrashEvent("mainFatalError", error.message, error.stack);
}
catch (telemetryError) {
logger.error("Failed to send crash telemetry", { telemetryError });
}
process.exit(1);
});