UNPKG

@debugg-ai/debugg-ai-mcp

Version:

Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.

195 lines (194 loc) 7.84 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { DebuggAIServerClient } from "./services/index.js"; import { E2eTestRunner } from "./e2e-agents/e2eRunner.js"; const createE2eTestTool = { name: "debugg_ai_test_page_changes", description: "Use DebuggAI to run & and test UI changes that have been made with its User emulation agents", inputSchema: { type: "object", properties: { description: { type: "string", description: "Description of what page (relative url) and features should be tested.", }, localPort: { type: "number", description: "Localhost port number where the app is running. Eg. 3000", }, filePath: { type: "string", description: "Absolute path to the file to test", }, repoName: { type: "string", description: "The name of the current repository", }, branchName: { type: "string", description: "Current branch name", }, repoPath: { type: "string", description: "Local path of the repo root", }, }, required: ["description",], }, }; async function configureTestRunner(client) { const e2eTestRunner = new E2eTestRunner(client); return e2eTestRunner; } const server = new Server({ name: "DebuggAI MCP Server", version: "0.1.1", }, { capabilities: { tools: {}, }, }); server.setRequestHandler(CallToolRequestSchema, async (req) => { console.error("Received CallToolRequest:", req); const apiKey = process.env.DEBUGGAI_API_KEY; const testUsername = process.env.TEST_USERNAME_EMAIL; const testPassword = process.env.TEST_USER_PASSWORD; if (!apiKey || !testUsername || !testPassword) { console.error("Missing one or more required environment variables: DEBUGGAI_API_KEY, TEST_USERNAME_EMAIL, TEST_USER_PASSWORD"); process.exit(1); } try { const { name, arguments: args } = req.params; if (name === "debugg_ai_test_page_changes") { const { description } = args; let localPort = parseInt(process.env.DEBUGGAI_LOCAL_PORT ?? "3000"); let repoName = process.env.DEBUGGAI_LOCAL_REPO_NAME ?? "test-user-repo/test-repo"; let branchName = process.env.DEBUGGAI_LOCAL_BRANCH_NAME ?? "main"; let repoPath = process.env.DEBUGGAI_LOCAL_REPO_PATH ?? "/Users/test-user-repo/test-repo"; let filePath = process.env.DEBUGGAI_LOCAL_FILE_PATH ?? "/Users/test-user-repo/test-repo/index.ts"; let progressToken = req.params._meta?.progressToken; if (args?.localPort) { localPort = args.localPort; } if (args?.repoName) { repoName = args.repoName; } if (args?.branchName) { branchName = args.branchName; } if (args?.repoPath) { repoPath = args.repoPath; } if (args?.filePath) { filePath = args.filePath; } if (args?.progressToken) { progressToken = args.progressToken; } if (progressToken == undefined) { console.error("No progress token found"); // progressToken = uuidv4(); } const client = new DebuggAIServerClient(process.env.DEBUGGAI_API_KEY ?? ""); const e2eTestRunner = await configureTestRunner(client); const e2eRun = await e2eTestRunner.createNewE2eTest(localPort, description, repoName, branchName, repoPath, filePath); if (!e2eRun) { console.error("Failed to create E2E test"); return { content: [ { type: "text", text: "Failed to create E2E test" }, ], }; } if (progressToken) { await server.notification({ method: "notifications/progress", params: { progress: 0, total: 20, progressToken, }, }); } const finalRun = await e2eTestRunner.handleE2eRun(e2eRun, async (update) => { console.error(`📢 Status: ${update.status}`); const curStep = update.conversations?.[0]?.messages?.length; const totalSteps = update.conversations?.[0]?.messages?.length; const updateMessage = curStep ? update.conversations?.[0]?.messages?.[curStep]?.jsonContent?.currentState?.nextGoal : undefined; if (progressToken) { await server.notification({ method: "notifications/progress", params: { progress: curStep, total: 20, progressToken, message: updateMessage }, }); } }); const testOutcome = finalRun?.outcome; const testDetails = finalRun?.conversations?.[0]?.messages?.map((message) => message.jsonContent?.currentState?.nextGoal); // const runGif = finalRun?.runGif; // let base64 = ""; // if (runGif) { // const response = await fetch(runGif); // const arrayBuffer = await response.arrayBuffer(); // base64 = Buffer.from(arrayBuffer).toString('base64'); // } // many clients don't support gif yet, so default to jpg const finalScreenshot = finalRun?.finalScreenshot; let baseEncodedImage = ""; if (finalScreenshot) { const response = await fetch(finalScreenshot); const arrayBuffer = await response.arrayBuffer(); baseEncodedImage = Buffer.from(arrayBuffer).toString('base64'); } const responseContent = [ { type: "text", text: JSON.stringify({ testOutcome, testDetails }, null, 2), } ]; if (baseEncodedImage) { responseContent.push({ type: "image", data: baseEncodedImage, mimeType: "image/png" }); } console.error("Response content:", responseContent); return { content: responseContent, }; } throw new Error(`Tool not found: ${name}`); } catch (err) { return { content: [ { type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }), }, ], }; } }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [createE2eTestTool], }; }); async function main() { console.error("Starting DebuggAI MCP server..."); const transport = new StdioServerTransport(); await server.connect(transport); console.error("DebuggAI MCP Server running on stdio"); } main().catch((err) => { console.error("Fatal error in main():", err); process.exit(1); });