# Directory Structure ``` ├── .dockerignore ├── .gitignore ├── dist │ ├── index.d.ts │ ├── index.js │ ├── index.js.map │ ├── prompts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── index.js.map │ ├── resources │ │ ├── docs.d.ts │ │ ├── docs.js │ │ ├── docs.js.map │ │ ├── templates.d.ts │ │ ├── templates.js │ │ └── templates.js.map │ ├── server.d.ts │ ├── server.js │ ├── server.js.map │ ├── tools │ │ ├── analyzer.d.ts │ │ ├── analyzer.js │ │ ├── analyzer.js.map │ │ ├── component-tester.d.ts │ │ ├── component-tester.js │ │ ├── component-tester.js.map │ │ ├── generator.d.ts │ │ ├── generator.js │ │ ├── generator.js.map │ │ ├── runner.d.ts │ │ ├── runner.js │ │ └── runner.js.map │ ├── transports │ │ ├── http.d.ts │ │ ├── http.js │ │ ├── http.js.map │ │ ├── stdio.d.ts │ │ ├── stdio.js │ │ └── stdio.js.map │ └── utils │ ├── command-executor.d.ts │ ├── command-executor.js │ ├── command-executor.js.map │ ├── test-config.d.ts │ ├── test-config.js │ ├── test-config.js.map │ ├── test-environment.d.ts │ ├── test-environment.js │ └── test-environment.js.map ├── docker-compose.yml ├── Dockerfile ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── index.ts │ ├── prompts │ │ └── index.ts │ ├── resources │ │ ├── docs.ts │ │ └── templates.ts │ ├── server.ts │ ├── tools │ │ ├── analyzer.ts │ │ ├── component-tester.ts │ │ ├── generator.ts │ │ └── runner.ts │ ├── transports │ │ ├── http.ts │ │ └── stdio.ts │ └── utils │ ├── command-executor.ts │ ├── test-config.ts │ └── test-environment.ts ├── templates │ ├── cypress │ │ └── component.txt │ └── jest │ ├── component.txt │ └── unit.txt └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules ``` -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- ``` # Node.js node_modules npm-debug.log yarn-debug.log yarn-error.log # TypeScript output dist build # Version control .git .gitignore # Docker Dockerfile docker-compose.yml .dockerignore # Environment variables .env .env.* # IDE specific files .idea .vscode *.swp *.swo # Logs logs *.log # Test coverage coverage .nyc_output ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # MCP Frontend Testing Server ## Description This MCP server provides tools for frontend testing, including: - **Code Analysis**: Analyzes JavaScript/TypeScript code to determine appropriate testing strategies. - **Test Generation**: Generates unit and component tests for Jest and Cypress. - **Test Running**: Executes tests using Jest and Cypress and returns results. - **Component Testing**: Provides a tool specifically for testing React components. ## Getting Started ### Installation 1. **Clone the repository:** \`git clone <repository-url> mcp-frontend-testing\` 2. **Navigate to the project directory:** \`cd mcp-frontend-testing\` 3. **Install dependencies:** \`npm install\` ### Running the Server #### HTTP Transport \`\`\`bash # Build the server npm run build # Start the server with HTTP transport npm run start:http \`\`\` #### Stdio Transport \`\`\`bash # Build the server npm run build # Start the server with Stdio transport npm run start:stdio \`\`\` ## Usage ### Tools - **analyzeCode**: Analyzes code and returns analysis results. - **Parameters**: - \`code\` (string, required): The source code to analyze. - \`language\` (enum, optional): Language of the code (\`javascript\` | \`typescript\` | \`jsx\` | \`tsx\`, default: \`javascript\`). - **generateTest**: Generates test code based on source code and framework. - **Parameters**: - \`code\` (string, required): The source code to generate tests for. - \`framework\` (enum, required): Testing framework (\`jest\` | \`cypress\`). - \`type\` (enum, required): Type of test (\`unit\` | \`component\` | \`e2e\`). - \`language\` (enum, optional): Language of the code (\`javascript\` | \`typescript\` | \`jsx\` | \`tsx\`, default: \`javascript\`). - \`description\` (string, optional): Description of the test case. - **runTest**: Runs tests and returns results. - **Parameters**: - \`sourceCode\` (string, required): The source code being tested. - \`testCode\` (string, required): The test code to execute. - \`framework\` (enum, required): Testing framework (\`jest\` | \`cypress\`). - \`type\` (enum, required): Type of test (\`unit\` | \`component\` | \`e2e\`). - \`config\` (record, optional): Configuration object for test execution. - **testReactComponent**: Runs component tests specifically for React components. - **Parameters**: - \`componentCode\` (string, required): The source code of the React component. - \`testCode\` (string, optional): Test code for the component (auto-generated if not provided). - \`framework\` (enum, optional): Testing framework (\`jest\` | \`cypress\`, default: \`jest\`). - \`props\` (record, optional): Props to pass to the component during testing. - \`autoGenerateTest\` (boolean, optional): Automatically generate test code if not provided (default: \`true\`). ### Resources - **templates**: Provides test templates. - **URI**: \`templates://{framework}/{type}\` - **Parameters**: - \`framework\` (string, required): Testing framework (\`jest\` | \`cypress\`). - \`type\` (string, required): Type of template (\`unit\` | \`component\`). - **docs**: Provides documentation for testing frameworks. - **URI**: \`docs://{topic}\` - **Parameters**: - \`topic\` (string, required): Documentation topic (\`jest\` | \`cypress\` | \`react-testing-library\`). ## Deployment ### Docker Build and run the server using Docker: \`\`\`bash docker build -t mcp-frontend-testing . docker run -p 3000:3000 mcp-frontend-testing \`\`\` ### Cloud Deploy to cloud platforms like AWS Lambda, Google Cloud Run, or Azure Functions for serverless or containerized deployments. --- **Note**: This server is designed to be used with an MCP client to enable LLMs to perform frontend testing tasks. ``` -------------------------------------------------------------------------------- /templates/jest/unit.txt: -------------------------------------------------------------------------------- ``` // Jest unit test template describe('Unit test', () => { test('should work correctly', () => { // Arrange // Act // Assert expect(true).toBe(true); }); }); ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml version: '3.8' services: mcp-frontend-testing: build: context: . dockerfile: Dockerfile image: mcp-frontend-testing container_name: mcp-frontend-testing ports: - "3000:3000" environment: - NODE_ENV=production restart: unless-stopped ``` -------------------------------------------------------------------------------- /templates/cypress/component.txt: -------------------------------------------------------------------------------- ``` // Cypress component test template import { mount } from '@cypress/react'; import Component from './Component'; describe('Component', () => { it('renders correctly', () => { // Arrange mount(<Component />); // Assert cy.contains(/example/i).should('be.visible'); }); }); ``` -------------------------------------------------------------------------------- /src/transports/stdio.ts: -------------------------------------------------------------------------------- ```typescript import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { createServer } from '../server.js'; export async function startStdioServer(): Promise<void> { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); } ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, "outDir": "dist", "sourceMap": true, "declaration": true, "jsx": "react-jsx", "lib": ["ES2022", "DOM"], "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` -------------------------------------------------------------------------------- /templates/jest/component.txt: -------------------------------------------------------------------------------- ``` // Jest React component test template import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Component from './Component'; describe('Component', () => { test('renders correctly', () => { // Arrange render(<Component />); // Assert expect(screen.getByText(/example/i)).toBeInTheDocument(); }); }); ``` -------------------------------------------------------------------------------- /src/utils/command-executor.ts: -------------------------------------------------------------------------------- ```typescript import { exec } from 'child_process'; import util from 'util'; const execPromise = util.promisify(exec); // Execute a command and return stdout export async function executeCommand(command: string, cwd: string): Promise<any> { return new Promise((resolve, reject) => { exec(command, { cwd }, (error, stdout, stderr) => { if (error) { reject(error); } resolve({ stdout, stderr }); }); }); } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Build stage FROM node:20-alpine AS build # Set working directory WORKDIR /app # Copy package files for dependency installation COPY package.json package-lock.json ./ # Install dependencies RUN npm ci # Copy the rest of the application code COPY . . # Build the TypeScript application RUN npm run build # Production stage FROM node:20-alpine AS production # Set working directory WORKDIR /app # Copy package files COPY package.json package-lock.json ./ # Install only production dependencies RUN npm ci --production # Copy built application from build stage COPY --from=build /app/dist ./dist COPY --from=build /app/templates ./templates # Expose the HTTP port EXPOSE 3000 # Set the default command to run the server with HTTP transport CMD ["node", "dist/index.js", "--transport=http"] ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { parseArgs } from 'node:util'; import { startHttpServer } from './transports/http.js'; import { startStdioServer } from './transports/stdio.js'; async function main() { // Parse command line arguments const { values } = parseArgs({ options: { transport: { type: 'string', short: 't', default: 'stdio' }, port: { type: 'string', short: 'p', default: '3000' } } }); // Start server with appropriate transport const transport = values.transport; if (transport === 'stdio') { await startStdioServer(); } else if (transport === 'http') { const port = parseInt(values.port); await startHttpServer(port); console.info(`HTTP server started on port ${port}`); } else { console.error(`Unknown transport: ${transport}`); process.exit(1); } } main().catch(err => { console.error('Error starting server:', err); process.exit(1); }); ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-frontend-testing", "version": "1.0.0", "description": "MCP server for frontend testing with Jest and Cypress", "main": "dist/index.js", "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "start:http": "node dist/index.js --transport=http", "start:stdio": "node dist/index.js --transport=stdio", "dev": "ts-node-esm src/index.ts", "test": "jest" }, "dependencies": { "@modelcontextprotocol/sdk": "1.10.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "16.3.0", "@testing-library/user-event": "^14.5.1", "cors": "^2.8.5", "cypress": "14.3.2", "esbuild": "0.25.3", "express": "5.0.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "react": "19.1.0", "react-dom": "19.1.0", "typescript": "5.8.3", "zod": "3.24.3" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "5.0.1", "@types/jest": "^29.5.11", "@types/node": "22.13.11", "@types/react": "19.1.2", "@types/react-dom": "19.1.2", "ts-node": "^10.9.1" } } ``` -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; // Import resources import { registerTemplateResources } from './resources/templates.js'; import { registerDocResources } from './resources/docs.js'; // Import tools import { registerAnalyzerTool } from './tools/analyzer.js'; import { registerGeneratorTool } from './tools/generator.js'; import { registerRunnerTool } from './tools/runner.js'; import { registerComponentTesterTool } from './tools/component-tester.js'; // Import prompts import { registerPrompts } from './prompts/index.js'; export function createServer(): McpServer { // Create MCP server const server = new McpServer({ name: 'Frontend Testing Server', version: '1.0.0', description: 'MCP server for testing JavaScript/TypeScript code and React components' }); // Register resources registerTemplateResources(server); registerDocResources(server); // Register tools registerAnalyzerTool(server); registerGeneratorTool(server); registerRunnerTool(server); registerComponentTesterTool(server); // Register prompts registerPrompts(server); return server; } ``` -------------------------------------------------------------------------------- /src/prompts/index.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; export function registerPrompts(server: McpServer): void { // Simplified implementation to avoid TypeScript errors // These prompts will be used by the LLM to generate test code // Unit test prompt server.prompt( 'create-unit-test', 'Create a unit test for the given code', () => ({ messages: [{ role: 'user', content: { type: 'text', text: 'Please create a unit test for the provided code.', }, }], }) ); // Component test prompt server.prompt( 'create-component-test', 'Create a test for a React component', () => ({ messages: [{ role: 'user', content: { type: 'text', text: 'Please create a test for this React component. Focus on testing the component\'s functionality, props, and user interactions.', }, }], }) ); // Fix failing test prompt server.prompt( 'fix-failing-test', 'Fix a failing test', () => ({ messages: [{ role: 'user', content: { type: 'text', text: 'I have a test that\'s failing. Please help me fix it by explaining what\'s wrong and providing a fixed version of the test.', }, }], }) ); } ``` -------------------------------------------------------------------------------- /src/transports/http.ts: -------------------------------------------------------------------------------- ```typescript import express from 'express'; import cors from 'cors'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { createServer } from '../server.js'; // Map to store active transports const transports = new Map<string, SSEServerTransport>(); export async function startHttpServer(port: number): Promise<void> { const app = express(); app.use(cors()); app.use(express.json()); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); // SSE endpoint app.get('/sse/:sessionId', async (req, res) => { const { sessionId } = req.params; res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // Create server and transport const server = createServer(); const transport = new SSEServerTransport('/messages', res); transports.set(sessionId, transport); // Connect the transport to the server await server.connect(transport); // Remove transport when connection closes req.on('close', () => { transports.delete(sessionId); }); }); // Message endpoint app.post('/messages/:sessionId', async (req, res) => { const { sessionId } = req.params; const transport = transports.get(sessionId); if (!transport) { res.status(404).json({ error: 'Session not found' }); return; } await transport.handlePostMessage(req, res); }); // Start server return new Promise((resolve) => { app.listen(port, () => { resolve(); }); }); } ``` -------------------------------------------------------------------------------- /src/tools/runner.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { setupTestEnvironment, executeTest, cleanupTestEnvironment } from '../utils/test-environment.js'; import crypto from 'crypto'; import path from 'path'; import os from 'os'; import { executeCommand } from '../utils/command-executor.js'; export { executeCommand }; export function registerRunnerTool(server: McpServer): void { server.tool( 'runTest', { sourceCode: z.string(), testCode: z.string(), framework: z.enum(['jest', 'cypress']), type: z.enum(['unit', 'component', 'e2e']), config: z.record(z.any()).optional() }, async ({ sourceCode, testCode, framework, type, config }) => { try { // Create temporary test environment const testId = crypto.randomUUID(); const testDir = path.join(os.tmpdir(), 'mcp-test-server', testId); // Set up files await setupTestEnvironment(testDir, sourceCode, testCode, framework, type, config); // Install dependencies await executeCommand('npm install', testDir); try { // Run the test const results = await executeTest(testDir, framework, type); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } finally { // Clean up await cleanupTestEnvironment(testDir); } } catch (error) { return { isError: true, content: [{ type: 'text', text: `Error running test: ${String(error)}` }] }; } } ); } ``` -------------------------------------------------------------------------------- /src/tools/component-tester.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { setupTestEnvironment, executeTest } from '../utils/test-environment.js'; import { generateTestCode } from './generator.js'; import { executeCommand } from '../utils/command-executor.js'; import crypto from 'crypto'; import path from 'path'; import os from 'os'; import { rm } from 'fs/promises'; export function registerComponentTesterTool(server: McpServer): void { server.tool( 'testReactComponent', { componentCode: z.string(), testCode: z.string().optional(), framework: z.enum(['jest', 'cypress']).default('jest'), props: z.record(z.any()).optional(), autoGenerateTest: z.boolean().default(true) }, async ({ componentCode, testCode, framework, props, autoGenerateTest }) => { try { // Determine language based on code const language = componentCode.includes('tsx') || componentCode.includes(':') ? 'tsx' : (componentCode.includes('jsx') ? 'jsx' : 'javascript'); // Generate test if not provided let finalTestCode = testCode; if (!finalTestCode && autoGenerateTest) { finalTestCode = await generateTestCode( componentCode, framework, 'component', language ); } if (!finalTestCode) { throw new Error('No test code provided or generated'); } // Create temporary test environment const testId = crypto.randomUUID(); const testDir = path.join(os.tmpdir(), 'mcp-test-server', testId); // Set up files for component testing await setupTestEnvironment(testDir, componentCode, finalTestCode, framework, 'component', props); // Install dependencies await executeCommand('npm install', testDir); try { // Run the test const results = await executeTest(testDir, framework, 'component'); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2), }], }; } finally { // Clean up try { await rm(testDir, { recursive: true, force: true }); } catch (error) { console.error('Error cleaning up test environment:', error); } } } catch (error) { return { isError: true, content: [{ type: 'text', text: `Error testing component: ${String(error)}`, }], }; } } ); } ``` -------------------------------------------------------------------------------- /src/resources/templates.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { readFile } from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; // Get directory name const __dirname = path.dirname(fileURLToPath(import.meta.url)); const templatesDir = path.join(__dirname, '../../templates'); export function registerTemplateResources(server: McpServer): void { // Register template resources server.resource( 'templates', new ResourceTemplate('templates://{framework}/{type}', { list: async () => { return { resources: [ { uri: 'templates://jest/component', name: 'Jest Component Test Template', mimeType: 'text/plain', description: 'Template for Jest React component tests' }, { uri: 'templates://jest/unit', name: 'Jest Unit Test Template', mimeType: 'text/plain', description: 'Template for Jest unit tests' }, { uri: 'templates://cypress/component', name: 'Cypress Component Test Template', mimeType: 'text/plain', description: 'Template for Cypress component tests' } ] }; } }), async (uri, { framework, type }) => { try { // Try to load the template file const filePath = path.join(templatesDir, framework as string, `${type as string}.txt`); const content = await readFile(filePath, 'utf-8'); return { contents: [{ uri: uri.href, text: content }] }; } catch (error) { // Fallback to hardcoded templates if file not found const templates: Record<string, Record<string, string>> = { jest: { unit: ` // Jest unit test template describe('Unit test', () => { test('should work correctly', () => { // Arrange // Act // Assert expect(true).toBe(true); }); });`, component: ` // Jest React component test template import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Component from './Component'; describe('Component', () => { test('renders correctly', () => { // Arrange render(<Component />); // Assert expect(screen.getByText(/example/i)).toBeInTheDocument(); }); });`, }, cypress: { component: ` // Cypress component test template import Component from './Component'; describe('Component', () => { it('renders correctly', () => { // Arrange cy.mount(<Component />); // Assert cy.contains(/example/i).should('be.visible'); }); });`, } }; const frameworkTemplates = templates[framework as keyof typeof templates]; const templateContent = frameworkTemplates && (type as string) in frameworkTemplates ? frameworkTemplates[type as string] : "Template not found"; return { contents: [{ uri: uri.href, text: templateContent }] }; } } ); // Template index server.resource( 'templates-index', 'templates://', async (uri) => { return { contents: [{ uri: uri.href, text: JSON.stringify({ frameworks: [ 'jest', 'cypress' ], types: [ 'unit', 'component' ] }, null, 2) }] }; } ); } ``` -------------------------------------------------------------------------------- /src/utils/test-config.ts: -------------------------------------------------------------------------------- ```typescript // Default Jest configuration for different test types export function getDefaultJestConfig( type: 'unit' | 'component' | 'e2e', isTypeScript: boolean ): string { const baseConfig: any = { transform: {}, testEnvironment: type === 'component' ? 'jsdom' : 'node', setupFilesAfterEnv: type === 'component' ? ['<rootDir>/setupTests.js'] : [], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], moduleDirectories: ['node_modules', '<rootDir>'], }; if (isTypeScript) { baseConfig.transform = {'\\.(ts|tsx)$': 'ts-jest'}; baseConfig.testRegex = '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$'; baseConfig.moduleFileExtensions = ['ts', 'tsx', 'js', 'jsx', 'json', 'node']; } else { baseConfig.transform = {'\\.(js|jsx)$': 'babel-jest'}; baseConfig.testRegex = '(/__tests__/.*|\\.(test|spec))\\.(js|jsx)$'; baseConfig.moduleFileExtensions = ['js', 'jsx', 'json', 'node']; } return `export default ${JSON.stringify(baseConfig, null, 2)};`; } // Default Cypress configuration for different test types export function getDefaultCypressConfig( type: 'unit' | 'component' | 'e2e', isTypeScript: boolean ): string { let config; if (type === 'component') { config = { component: { devServer: { framework: 'react', bundler: 'vite', }, specPattern: isTypeScript ? '**/*.cy.{js,jsx,ts,tsx}' : '**/*.cy.{js,jsx}', }, }; } else { config = { e2e: { setupNodeEvents(on: any, config: any) { return config; }, specPattern: isTypeScript ? 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}' : 'cypress/e2e/**/*.cy.{js,jsx}', }, }; } return `import { defineConfig } from 'cypress' export default defineConfig(${JSON.stringify(config, null, 2)})`; } // Generate component wrappers for testing with props export function getComponentTestWrappers( componentName: string, isTypeScript: boolean ): { wrapperCode: string, importStatement: string } { const importStatement = isTypeScript ? `import React from 'react';` : `import React from 'react';`; const wrapperCode = isTypeScript ? `export const TestWrapper: React.FC = () => { return <${componentName} {...testProps} />; }; export default TestWrapper;` : `export const TestWrapper = () => { return <${componentName} {...testProps} />; }; export default TestWrapper;`; return { wrapperCode, importStatement }; } // Get dependencies for package.json based on test configuration export function getDependencies( framework: string, type: string, isTypeScript: boolean, isReact: boolean ): Record<string, string> { const dependencies: Record<string, string> = { // Common dependencies "jest": "^29.7.0", }; // TypeScript dependencies if (isTypeScript) { dependencies["typescript"] = "^5.3.3"; dependencies["ts-jest"] = "^29.1.1"; dependencies["@types/jest"] = "^29.5.11"; } // React dependencies if (isReact) { dependencies["react"] = "^18.2.0"; dependencies["react-dom"] = "^18.2.0"; if (isTypeScript) { dependencies["@types/react"] = "^18.2.42"; dependencies["@types/react-dom"] = "^18.2.17"; } } // Framework-specific dependencies if (framework === 'jest') { dependencies["jest-environment-jsdom"] = "^29.7.0"; if (type === 'component' || isReact) { dependencies["@testing-library/react"] = "^14.1.2"; dependencies["@testing-library/jest-dom"] = "^6.1.5"; dependencies["@testing-library/user-event"] = "^14.5.1"; } } else if (framework === 'cypress') { dependencies["cypress"] = "^13.6.1"; if (type === 'component' || isReact) { dependencies["@cypress/react"] = "^7.0.3"; } } // Build tools dependencies["esbuild"] = "^0.19.9"; return dependencies; } ``` -------------------------------------------------------------------------------- /src/resources/docs.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; export function registerDocResources(server: McpServer): void { server.resource( 'docs', new ResourceTemplate('docs://{topic}', { list: async () => { return { resources: [ { uri: 'docs://jest', name: 'Jest Documentation', mimeType: 'text/plain', description: 'Documentation for Jest testing framework' }, { uri: 'docs://cypress', name: 'Cypress Documentation', mimeType: 'text/plain', description: 'Documentation for Cypress testing framework' }, { uri: 'docs://react-testing-library', name: 'React Testing Library Documentation', mimeType: 'text/plain', description: 'Documentation for React Testing Library' } ] }; } }), async (uri, { topic }) => { const docs: Record<string, string> = { jest: ` # Jest Documentation Jest is a JavaScript testing framework designed to ensure correctness of any JavaScript codebase. It allows you to write tests with an approachable, familiar and feature-rich API that gives you results quickly. ## Key Features - Zero config for most JavaScript projects - Snapshots for tracking large objects - Isolated test files to avoid sharing state - Powerful mocking library ## Basic Example \`\`\`javascript // sum.js function sum(a, b) { return a + b; } module.exports = sum; // sum.test.js const sum = require('./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); \`\`\` `, cypress: ` # Cypress Documentation Cypress is a next generation front end testing tool built for the modern web. It enables you to write faster, easier and more reliable tests. ## Key Features - Time Travel: Cypress takes snapshots as your tests run - Debuggability: Debug directly from familiar tools like Chrome DevTools - Automatic Waiting: Cypress automatically waits for commands and assertions - Real-time Reloads: Test is automatically reloaded when you make changes ## Basic Example \`\`\`javascript describe('My First Test', () => { it('clicks the link "type"', () => { cy.visit('https://example.cypress.io') cy.contains('type').click() cy.url().should('include', '/commands/actions') }) }) \`\`\` `, 'react-testing-library': ` # React Testing Library Documentation React Testing Library is a very light-weight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils, encouraging better testing practices. ## Key Features - Works with actual DOM nodes - Focuses on testing from the user perspective - Encourages accessibility best practices - Simple and intuitive API ## Basic Example \`\`\`javascript import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import Component from './Component.js'; test('loads and displays greeting', async () => { // Arrange render(<Component />) // Act await userEvent.click(screen.getByText('Load Greeting')) // Assert expect(screen.getByRole('heading')).toHaveTextContent('hello there') }) \`\`\` ` }; return { contents: [{ uri: uri.href, text: docs[topic as keyof typeof docs] || 'Documentation not found' }] }; } ); // Documentation index server.resource( 'docs-index', 'docs://', async (uri) => { return { contents: [{ uri: uri.href, text: JSON.stringify({ topics: [ 'jest', 'cypress', 'react-testing-library' ] }, null, 2) }] }; } ); } ``` -------------------------------------------------------------------------------- /src/tools/analyzer.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; export function registerAnalyzerTool(server: McpServer): void { server.tool( 'analyzeCode', { code: z.string(), language: z.enum(['javascript', 'typescript', 'jsx', 'tsx']).default('javascript') }, async ({ code, language }) => { try { // Analyze code to determine what kind of tests would be appropriate const analysis = performCodeAnalysis(code, language); return { content: [{ type: 'text', text: JSON.stringify(analysis, null, 2), }], }; } catch (error) { return { isError: true, content: [{ type: 'text', text: `Error analyzing code: ${String(error)}`, }], }; } } ); } // Helper function to analyze code // eslint-disable-next-line @typescript-eslint/no-explicit-any export function performCodeAnalysis(code: string, language: string): any { const analysisResult: any = { codeType: {}, complexity: {}, recommendations: {} }; try { // Determine if the code is a React component const isReactComponent = code.includes('import React') || code.includes('from "react"') || code.includes("from 'react'") || code.includes('extends Component') || code.includes('React.Component') || ((code.includes('export') && code.includes('return')) && (code.includes('JSX.') || code.includes('<div') || code.includes('<>'))); // Check if it's a function or class const isClass = code.includes('class ') && code.includes('extends '); const isFunction = code.includes('function ') || code.includes('=>'); // Check if it uses hooks const usesHooks = code.includes('useState') || code.includes('useEffect') || code.includes('useContext') || code.includes('useReducer') || code.includes('useCallback') || code.includes('useMemo'); // Count imports to determine complexity const importMatches = code.match(/import .+ from .+/g); const imports = importMatches ? importMatches.length : 0; // Look for event handlers const hasEvents = code.includes('onClick') || code.includes('onChange') || code.includes('onSubmit') || code.includes('addEventListener'); // Look for async operations const hasAsync = code.includes('async ') || code.includes('await ') || code.includes('Promise') || code.includes('.then(') || code.includes('fetch('); const recommendedTestTypes: string[] = []; if (isReactComponent) { recommendedTestTypes.push('component'); if (hasEvents || hasAsync) { recommendedTestTypes.push('e2e'); } else { recommendedTestTypes.push('unit'); } } else { recommendedTestTypes.push('unit'); } // Recommend testing frameworks const recommendedFrameworks: string[] = []; if (isReactComponent) { recommendedFrameworks.push('jest'); if (hasEvents) { recommendedFrameworks.push('cypress'); } else { recommendedFrameworks.push('jest'); } } else { recommendedFrameworks.push('jest'); } analysisResult.codeType = { isReactComponent, isClass, isFunction, usesHooks, }; analysisResult.complexity = { imports, hasEvents, hasAsync }; analysisResult.recommendations = { testTypes: recommendedTestTypes, frameworks: recommendedFrameworks, priority: hasAsync ? 'high' : 'medium' }; } catch (error: any) { console.error(`Error during code analysis: ${error.message}`); } return analysisResult; } ``` -------------------------------------------------------------------------------- /src/tools/generator.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { performCodeAnalysis } from './analyzer.js'; export function registerGeneratorTool(server: McpServer): void { server.tool( 'generateTest', { code: z.string(), framework: z.enum(['jest', 'cypress']), type: z.enum(['unit', 'component', 'e2e']), language: z.enum(['javascript', 'typescript', 'jsx', 'tsx']).default('javascript'), description: z.string().optional() }, async ({ code, framework, type, language, description }) => { try { const testCode = generateTestCode(code, framework, type, language, description); return { content: [{ type: 'text', text: testCode, }], }; } catch (error) { return { isError: true, content: [{ type: 'text', text: `Error generating test: ${String(error)}`, }], }; } } ); } // Helper function to generate test code based on source code and parameters // eslint-disable-next-line @typescript-eslint/no-explicit-any export function generateTestCode( sourceCode: string, framework: string, testType: string, language: string, description?: string ): string { // Analyze the code to better understand its structure const analysis = performCodeAnalysis(sourceCode, language); // Extract component or function name const nameMatch = sourceCode.match(/(?:function|class|const)\s+(\w+)/); const name = nameMatch ? nameMatch[1] : 'Component'; // Generate appropriate import statements based on framework and test type let imports = ''; let testCode = ''; if (framework === 'jest') { if (testType === 'unit') { imports = `// Import the module to test\n${language.includes('typescript') ? `import { ${name} } from './${name}';` : `const ${name} = require('./${name}');`}`; testCode = `describe('${name}', () => { test('${description || 'should work correctly'}', () => { // Arrange ${(analysis as any)?.codeType?.isFunction ? ` // Example test input const input = 'test'; // Act const result = ${name}(input); // Assert expect(result).toBeDefined();` : ` // Setup any required state // Act - perform the action // Assert - check the result expect(true).toBe(true);`} }); ${(analysis as any)?.complexity?.hasAsync ? ` test('handles async operations', async () => { // Arrange // Act const result = await ${name}(); // Assert expect(result).toBeDefined(); });` : ''} });`; } else if (testType === 'component') { imports = `import { render, screen${(analysis as any)?.complexity?.hasEvents ? ', fireEvent' : ''} } from '@testing-library/react';\n${(analysis as any)?.complexity?.hasEvents ? `import userEvent from '@testing-library/user-event';` : ''}\n${language.includes('typescript') ? `import { ${name} } from './${name}';` : `import { default as ${name} } from './${name}';`}`; testCode = `describe('${name}', () => { test('renders correctly', () => { // Arrange render(<${name} />); // Assert expect(screen.getByText(/content/i)).toBeInTheDocument(); }); ${(analysis as any).complexity?.hasEvents ? ` test('handles user interaction', async () => { // Arrange render(<${name} />); // Act await userEvent.click(screen.getByRole('button')); // Assert expect(screen.getByText(/result/i)).toBeInTheDocument(); });` : ''} ${(analysis as any)?.complexity?.hasAsync ? ` test('loads data asynchronously', async () => { // Arrange render(<${name} />); // Act - wait for async operation await screen.findByText(/loaded/i); // Assert expect(screen.getByText(/loaded/i)).toBeInTheDocument(); });` : ''} });`; } } else if (framework === 'cypress') { if (testType === 'component') { imports = `${language.includes('typescript') ? `import { ${name} } from './${name}';` : `import Component from './Component';`}`; testCode = `describe('${name}', () => { it('renders correctly', () => { // Arrange cy.mount(<${name} />); // Assert cy.contains(/content/i).should('be.visible'); }); ${(analysis as any)?.complexity?.hasEvents ? ` it('handles user interaction', () => { // Arrange cy.mount(<${name} />); // Act cy.get('button').click(); // Assert cy.contains(/result/i).should('be.visible'); });` : ''} ${(analysis as any)?.complexity?.hasAsync ? ` it('loads data asynchronously', () => { // Arrange cy.mount(<${name} />); // Assert - wait for async operation cy.contains(/loaded/i, { timeout: 10000 }).should('be.visible'); });` : ''} });`; } else if (testType === 'e2e') { imports = '// No imports needed for Cypress E2E tests'; testCode = `describe('${name} E2E Test', () => { beforeEach(() => { // Visit the page containing the component cy.visit('/'); }); it('${description || 'works correctly'}', () => { // Assert the component is rendered cy.contains(/content/i).should('be.visible'); ${(analysis as any)?.complexity?.hasEvents ? ` // Act - interact with the component cy.get('button').click(); // Assert the interaction worked cy.contains(/result/i).should('be.visible');` : ''} ${(analysis as any)?.complexity?.hasAsync ? ` // Assert async data loads correctly cy.contains(/loaded/i, { timeout: 10000 }).should('be.visible');` : ''} }); });`; } } // Combine imports and test code return `${imports}\n${testCode}`; } ``` -------------------------------------------------------------------------------- /src/utils/test-environment.ts: -------------------------------------------------------------------------------- ```typescript import { mkdir, writeFile, rm } from 'fs/promises'; import path from 'path'; import { exec } from 'child_process'; import util from 'util'; import { getDefaultJestConfig, getDefaultCypressConfig, getComponentTestWrappers, getDependencies } from './test-config.js'; import { executeCommand } from './command-executor.js'; const execPromise = util.promisify(exec); // Set up a test environment with all necessary files export async function setupTestEnvironment( testDir: string, sourceCode: string, testCode: string, framework: 'jest' | 'cypress', type: 'unit' | 'component' | 'e2e', config?: Record<string, any> ): Promise<void> { // Create directory structure await mkdir(testDir, { recursive: true }); // Determine file extensions const isTypeScript = sourceCode.includes('typescript') || sourceCode.includes('tsx') || sourceCode.includes(':') || sourceCode.includes('interface'); const isReact = sourceCode.includes('React') || sourceCode.includes('react') || sourceCode.includes('JSX') || sourceCode.includes('<div') || sourceCode.includes('</'); const sourceExt = isTypeScript ? (isReact ? '.tsx' : '.ts') : (isReact ? '.jsx' : '.js'); const testExt = isTypeScript ? (framework === 'jest' ? '.test.tsx' : '.cy.tsx') : (framework === 'jest' ? '.test.jsx' : '.cy.jsx'); // Extract component or function name const nameMatch = sourceCode.match(/(?:function|class|const)\s+(\w+)/); const name = nameMatch ? nameMatch[1] : 'Component'; // Write source file await writeFile(path.join(testDir, `${name}${sourceExt}`), sourceCode); // Write test file await writeFile(path.join(testDir, `${name}${testExt}`), testCode); // Write configuration files if (framework === 'jest') { await writeFile( path.join(testDir, 'jest.config.js'), config?.jestConfig || getDefaultJestConfig(type, isTypeScript) as string ); // Setup for React testing if (isReact) { await writeFile( path.join(testDir, 'setupTests.js'), `import '@testing-library/jest-dom';` ); } } else if (framework === 'cypress') { await writeFile( path.join(testDir, 'cypress.config.js'), config?.cypressConfig || getDefaultCypressConfig(type, isTypeScript) as string ); // Create cypress directory structure for e2e tests if (type === 'e2e') { await mkdir(path.join(testDir, 'cypress', 'e2e'), { recursive: true }); await writeFile( path.join(testDir, 'cypress', 'e2e', `${name}.cy.js`), testCode ); } // Setup for component testing if (type === 'component') { await mkdir(path.join(testDir, 'cypress', 'support'), { recursive: true }); await writeFile( path.join(testDir, 'cypress', 'support', 'component.js'), `import { mount } from 'cypress/react18' import './commands' Cypress.Commands.add('mount', mount)` ); await writeFile( path.join(testDir, 'cypress', 'support', 'commands.js'), `// Custom commands go here` ); } } // Set up package.json await writeFile( path.join(testDir, 'package.json'), JSON.stringify({ name: 'mcp-test', version: '1.0.0', type: 'module', dependencies: getDependencies(framework, type, isTypeScript, isReact), scripts: { test: framework === 'jest' ? 'jest' : 'cypress run' } }, null, 2) ); // Create a basic index.html file for e2e tests if (framework === 'cypress' && type === 'e2e') { await writeFile( path.join(testDir, 'index.html'), `<!DOCTYPE html> <html> <head> <title>Test Page</title> </head> <body> <div id="root"></div> <script type="module" src="./index.js"></script> </body> </html>` ); await writeFile( path.join(testDir, 'index.js'), `import React from 'react'; import ReactDOM from 'react-dom/client'; import ${name} from './${name}'; ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <${name} /> </React.StrictMode> );` ); } } // Set up environment specifically for component testing export async function setupComponentTestEnvironment( testDir: string, componentCode: string, testCode: string, framework: 'jest' | 'cypress', props?: Record<string, any> ): Promise<void> { // Determine if TypeScript const isTypeScript = componentCode.includes('typescript') || componentCode.includes('tsx') || componentCode.includes(':'); // Create directory structure await mkdir(testDir, { recursive: true }); // Extract component name const nameMatch = componentCode.match(/(?:function|class|const)\s+(\w+)/); const name = nameMatch ? nameMatch[1] : 'Component'; // Determine file extensions const sourceExt = isTypeScript ? '.tsx' : '.jsx'; const testExt = isTypeScript ? (framework === 'jest' ? '.test.tsx' : '.cy.tsx') : (framework === 'jest' ? '.test.jsx' : '.cy.jsx'); // Write component file await writeFile(path.join(testDir, `${name}${sourceExt}`), componentCode); // Write test file await writeFile(path.join(testDir, `${name}${testExt}`), testCode); // Create props file if props provided if (props) { await writeFile( path.join(testDir, 'props.json'), JSON.stringify(props, null, 2) ); // Create a wrapper component for the tests const { wrapperCode, importStatement } = getComponentTestWrappers(name, isTypeScript); await writeFile( path.join(testDir, `TestWrapper${sourceExt}`), `${importStatement} import ${name} from './${name}'; import testProps from './props.json'; ${wrapperCode}` ); } // Set up configuration if (framework === 'jest') { await writeFile( path.join(testDir, 'jest.config.js'), getDefaultJestConfig('component', isTypeScript) as string ); await writeFile( path.join(testDir, 'setupTests.js'), `import '@testing-library/jest-dom';` ); } else { await writeFile( path.join(testDir, 'cypress.config.js'), getDefaultCypressConfig('component', isTypeScript) as string ); await mkdir(path.join(testDir, 'cypress', 'support'), { recursive: true }); await writeFile( path.join(testDir, 'cypress', 'support', 'component.js'), `import { mount } from 'cypress/react18' import './commands' Cypress.Commands.add('mount', mount)` ); await writeFile( path.join(testDir, 'cypress', 'support', 'commands.js'), `// Custom commands go here` ); } // Set up package.json await writeFile( path.join(testDir, 'package.json'), JSON.stringify({ name: 'mcp-component-test', version: '1.0.0', type: 'module', dependencies: getDependencies(framework, 'component', isTypeScript, true), scripts: { test: framework === 'jest' ? 'jest' : 'cypress run-component' } }, null, 2) ); } // Execute tests and return results export async function executeTest( testDir: string, framework: 'jest' | 'cypress', type: 'unit' | 'component' | 'e2e' ): Promise<any> { // Change to test directory const cwd = process.cwd(); process.chdir(testDir); try { // Install dependencies console.info('Installing dependencies...'); await executeCommand('npm install --silent', testDir); // Run tests console.info(`Running ${framework} ${type} tests...`); let result; if (framework === 'jest') { result = await executeCommand('npx jest --json', testDir); return JSON.parse(result.stdout); } else if (framework === 'cypress') { if (type === 'component') { result = await executeCommand('npx cypress run-component --reporter json', testDir); } else { result = await executeCommand('npx cypress run --reporter json', testDir); } return result.stdout ? JSON.parse(result.stdout) : { success: false, error: result.stderr }; } } catch (error) { console.error('Test execution error:', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } finally { // Change back to original directory process.chdir(cwd); } } // Execute component tests export async function executeComponentTest( testDir: string, framework: 'jest' | 'cypress' ): Promise<any> { return executeTest(testDir, framework, 'component'); } // Clean up test environment export async function cleanupTestEnvironment(testDir: string): Promise<void> { try { await rm(testDir, { recursive: true, force: true }); } catch (error) { console.error('Error cleaning up test environment:', error); } } ```