Skip to main content
Glama
giri-jeedigunta

Test Analyzer MCP Server

get_test_summary

Analyze JavaScript/TypeScript test setups to generate comprehensive summaries of test coverage, framework detection, and actionable improvement recommendations.

Instructions

Get a comprehensive summary of the test setup, coverage, and recommendations

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
repoPathYesPath to the repository

Implementation Reference

  • The primary handler function that executes the 'get_test_summary' tool. Validates repoPath input, orchestrates test setup analysis and coverage checking, then generates and returns a comprehensive summary report.
    private async getTestSummary(args: any) {
      if (!args.repoPath || typeof args.repoPath !== 'string') {
        throw new McpError(ErrorCode.InvalidParams, 'repoPath is required');
      }
    
      try {
        const repoPath = path.resolve(args.repoPath);
        
        // Get test setup analysis
        const setupResult = await this.analyzeTestSetup({ repoPath });
        const setup = JSON.parse(setupResult.content[0].text);
        
        // Get coverage data
        const coverageResult = await this.checkCoverage({ repoPath, runTests: false });
        const coverage = coverageResult.content[0].text.includes('No coverage data') 
          ? null 
          : JSON.parse(coverageResult.content[0].text);
        
        // Generate comprehensive summary
        const summary = this.generateComprehensiveSummary(setup, coverage);
        
        return {
          content: [
            {
              type: 'text',
              text: summary,
            },
          ],
        };
      } catch (error) {
        if (error instanceof McpError) throw error;
        
        return {
          content: [
            {
              type: 'text',
              text: `Error generating summary: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        };
      }
    }
  • src/index.ts:155-170 (registration)
    The CallToolRequestSchema handler with switch statement that dispatches 'get_test_summary' calls to the getTestSummary method.
      this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
        switch (request.params.name) {
          case 'analyze_test_setup':
            return await this.analyzeTestSetup(request.params.arguments);
          case 'check_coverage':
            return await this.checkCoverage(request.params.arguments);
          case 'get_test_summary':
            return await this.getTestSummary(request.params.arguments);
          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${request.params.name}`
            );
        }
      });
    }
  • src/index.ts:138-151 (registration)
    Registers the 'get_test_summary' tool in the ListToolsRequestSchema response, providing name, description, and input schema.
    {
      name: 'get_test_summary',
      description: 'Get a comprehensive summary of the test setup, coverage, and recommendations',
      inputSchema: {
        type: 'object',
        properties: {
          repoPath: {
            type: 'string',
            description: 'Path to the repository',
          },
        },
        required: ['repoPath'],
      },
    },
  • Input schema definition for the 'get_test_summary' tool, specifying a required 'repoPath' string parameter.
    inputSchema: {
      type: 'object',
      properties: {
        repoPath: {
          type: 'string',
          description: 'Path to the repository',
        },
      },
      required: ['repoPath'],
    },
  • Key helper function called by the handler to generate the final comprehensive markdown summary report with sections for setup, coverage, dependencies, and recommendations.
      }
    
      private async detectTestFramework(repoPath: string): Promise<TestFramework | null> {
        for (const framework of this.frameworks) {
          for (const configFile of framework.configFiles) {
            const configPath = path.join(repoPath, configFile);
            try {
              await fs.access(configPath);
              
              // Special check for Jest in package.json
              if (configFile === 'package.json' && framework.name === 'jest') {
                const packageJson = JSON.parse(await fs.readFile(configPath, 'utf-8'));
                if (packageJson.jest || (packageJson.scripts && Object.values(packageJson.scripts).some((script: any) => script.includes('jest')))) {
                  return framework;
                }
              } else {
                return framework;
              }
            } catch {
              // Config file doesn't exist, continue checking
            }
          }
        }
        
        // Check package.json for test dependencies
        try {
          const packageJsonPath = path.join(repoPath, 'package.json');
          const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
          const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
          
          for (const framework of this.frameworks) {
            if (allDeps[framework.name]) {
              return framework;
            }
          }
        } catch {
          // No package.json or error reading it
        }
        
        return null;
      }
    
      private async findTestFiles(repoPath: string, framework: TestFramework | null): Promise<string[]> {
        const patterns = framework?.testFilePatterns || [
          '**/*.test.{js,jsx,ts,tsx}',
          '**/*.spec.{js,jsx,ts,tsx}',
          '**/__tests__/**/*.{js,jsx,ts,tsx}',
        ];
        
        const testFiles: string[] = [];
        
        for (const pattern of patterns) {
          const files = await glob(pattern, {
            cwd: repoPath,
            absolute: true,
            ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**'],
          });
          testFiles.push(...files);
        }
        
        // Remove duplicates
        return [...new Set(testFiles)];
      }
    
      private async analyzeTestStructure(testFiles: string[]): Promise<any> {
        let suites = 0;
        let tests = 0;
        const hooks = new Set<string>();
        
        for (const file of testFiles) {
          try {
            const content = await fs.readFile(file, 'utf-8');
            const ast = parser.parse(content, {
              sourceType: 'module',
              plugins: ['jsx', 'typescript'],
            });
            
            traverse.default(ast, {
              CallExpression(path: any) {
                const callee = path.node.callee;
                if (callee.type === 'Identifier') {
                  const name = callee.name;
                  
                  // Count test suites
                  if (['describe', 'suite', 'context'].includes(name)) {
                    suites++;
                  }
                  
                  // Count individual tests
                  if (['it', 'test', 'specify'].includes(name)) {
                    tests++;
                  }
                  
                  // Track hooks
                  if (['beforeEach', 'afterEach', 'beforeAll', 'afterAll', 'before', 'after'].includes(name)) {
                    hooks.add(name);
                  }
                }
              },
            });
          } catch {
            // Error parsing file, skip it
          }
        }
        
        return {
          suites,
          tests,
          hooks: Array.from(hooks),
        };
      }
    
      private async getCoverageConfig(repoPath: string, framework: TestFramework | null): Promise<any> {
        const configs: any = {};
        
        // Check for NYC/Istanbul config
        const nycConfigFiles = ['.nycrc', '.nycrc.json', '.nycrc.yml', '.nycrc.yaml'];
        for (const configFile of nycConfigFiles) {
          try {
            const configPath = path.join(repoPath, configFile);
            const content = await fs.readFile(configPath, 'utf-8');
            configs.nyc = JSON.parse(content);
            break;
          } catch {
            // Config doesn't exist or can't be parsed
          }
        }
        
        // Check package.json for coverage config
        try {
          const packageJsonPath = path.join(repoPath, 'package.json');
          const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
          
          if (packageJson.nyc) {
            configs.nyc = packageJson.nyc;
          }
          
          if (packageJson.jest?.collectCoverage !== undefined || packageJson.jest?.coverageDirectory) {
            configs.jest = packageJson.jest;
          }
        } catch {
          // No package.json or error reading it
        }
        
        // Check for framework-specific coverage config
        if (framework?.name === 'jest') {
          try {
            const jestConfigPath = path.join(repoPath, 'jest.config.js');
            await fs.access(jestConfigPath);
            configs.jestConfigFile = true;
          } catch {
            // No jest.config.js
          }
        }
        
        return configs;
      }
    
      private async getTestDependencies(repoPath: string): Promise<string[]> {
        const testDeps: string[] = [];
        
        try {
          const packageJsonPath = path.join(repoPath, 'package.json');
          const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
          const devDeps = packageJson.devDependencies || {};
          
          const testRelatedPackages = [
            'jest', 'vitest', 'mocha', 'chai', 'sinon', 'cypress', 'playwright',
            '@testing-library/react', '@testing-library/jest-dom', '@testing-library/user-event',
            'enzyme', 'react-test-renderer', 'karma', 'jasmine', 'qunit',
            'nyc', 'c8', 'istanbul', '@vitest/coverage-c8', '@vitest/coverage-istanbul',
          ];
          
          for (const pkg of testRelatedPackages) {
            if (devDeps[pkg]) {
              testDeps.push(`${pkg}@${devDeps[pkg]}`);
            }
          }
        } catch {
          // Error reading package.json
        }
        
        return testDeps;
      }
    
      private async runTestsWithCoverage(repoPath: string): Promise<CoverageResult | null> {
        try {
          // Try common test coverage commands
          const commands = [
            'npm run test:coverage',
            'npm run coverage',
            'npm test -- --coverage',
            'yarn test:coverage',
            'yarn coverage',
            'yarn test --coverage',
            'npx jest --coverage',
            'npx vitest run --coverage',
          ];
          
          for (const cmd of commands) {
            try {
              const { stdout } = await execAsync(cmd, { cwd: repoPath });
              
              // Try to parse coverage from output
              const coverage = this.parseCoverageFromOutput(stdout);
              if (coverage) {
                return coverage;
              }
            } catch {
              // Command failed, try next one
            }
          }
          
          return null;
        } catch {
          return null;
        }
      }
    
      private async readExistingCoverage(repoPath: string): Promise<CoverageResult | null> {
        // Common coverage output locations
        const coverageFiles = [
          'coverage/coverage-summary.json',
          'coverage/lcov-report/index.html',
          'coverage-final.json',
          '.nyc_output/processinfo/index.json',
        ];
        
        for (const file of coverageFiles) {
          try {
            const coveragePath = path.join(repoPath, file);
            const content = await fs.readFile(coveragePath, 'utf-8');
            
            if (file.endsWith('.json')) {
              const data = JSON.parse(content);
              
              // Parse coverage-summary.json format
              if (data.total) {
                return {
                  lines: data.total.lines,
                  statements: data.total.statements,
                  functions: data.total.functions,
                  branches: data.total.branches,
                  summary: this.generateCoverageSummary(data.total),
                };
              }
            }
          } catch {
            // File doesn't exist or can't be parsed
          }
        }
        
        return null;
      }
    
      private parseCoverageFromOutput(output: string): CoverageResult | null {
        // Try to parse coverage table from console output
        const lines = output.split('\n');
        
        for (let i = 0; i < lines.length; i++) {
          const line = lines[i];
          
          // Look for coverage summary patterns
          if (line.includes('Statements') && line.includes('%')) {
            try {
              // Parse Jest/Vitest style output
              const stmtMatch = line.match(/Statements\s*:\s*([\d.]+)%/);
              const branchMatch = lines[i + 1]?.match(/Branches\s*:\s*([\d.]+)%/);
              const funcMatch = lines[i + 2]?.match(/Functions\s*:\s*([\d.]+)%/);
              const lineMatch = lines[i + 3]?.match(/Lines\s*:\s*([\d.]+)%/);
              
              if (stmtMatch) {
                const result: CoverageResult = {
                  statements: { percentage: parseFloat(stmtMatch[1]), covered: 0, total: 0 },
                  branches: { percentage: parseFloat(branchMatch?.[1] || '0'), covered: 0, total: 0 },
                  functions: { percentage: parseFloat(funcMatch?.[1] || '0'), covered: 0, total: 0 },
                  lines: { percentage: parseFloat(lineMatch?.[1] || '0'), covered: 0, total: 0 },
                  summary: '',
                };
                
                result.summary = this.generateCoverageSummary(result);
                return result;
              }
            } catch {
              // Continue searching
            }
          }
        }
        
        return null;
      }
    
      private generateCoverageSummary(coverage: any): string {
        const getStatus = (percentage: number) => {
          if (percentage >= 80) return '✅ Good';
          if (percentage >= 60) return '⚠️ Fair';
          return '❌ Poor';
        };
        
        const lines = coverage.lines?.percentage || coverage.lines?.pct || 0;
        const statements = coverage.statements?.percentage || coverage.statements?.pct || 0;
        const functions = coverage.functions?.percentage || coverage.functions?.pct || 0;
        const branches = coverage.branches?.percentage || coverage.branches?.pct || 0;
        
        return `Coverage Summary:
    - Lines: ${lines.toFixed(1)}% ${getStatus(lines)}
    - Statements: ${statements.toFixed(1)}% ${getStatus(statements)}
    - Functions: ${functions.toFixed(1)}% ${getStatus(functions)}
    - Branches: ${branches.toFixed(1)}% ${getStatus(branches)}
    
    Overall: ${getStatus((lines + statements + functions + branches) / 4)}`;
      }
    
      private generateTestSetupSummary(data: any): string {
        const { framework, testFiles, testStructure, coverageConfig, dependencies } = data;
        
        let summary = `Test Setup Analysis:\n\n`;
        
        summary += `📦 Framework: ${framework || 'None detected'}\n`;
        summary += `📁 Test Files: ${testFiles.length} files found\n`;
        summary += `🧪 Tests: ${testStructure.tests} tests in ${testStructure.suites} suites\n`;
        
        if (testStructure.hooks.length > 0) {
          summary += `🔧 Hooks: ${testStructure.hooks.join(', ')}\n`;
        }
        
        if (Object.keys(coverageConfig).length > 0) {
          summary += `\n📊 Coverage Configuration:\n`;
          Object.keys(coverageConfig).forEach((key: string) => {
            summary += `  - ${key}: Configured\n`;
          });
        }
        
        if (dependencies.length > 0) {
          summary += `\n📚 Test Dependencies:\n`;
          dependencies.slice(0, 10).forEach((dep: string) => {
            summary += `  - ${dep}\n`;
          });
          if (dependencies.length > 10) {
            summary += `  ... and ${dependencies.length - 10} more\n`;
          }
        }
        
        return summary;
      }
    
      private generateComprehensiveSummary(setup: TestAnalysisResult, coverage: CoverageResult | null): string {

Tool Definition Quality

Score is being calculated. Check back soon.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/giri-jeedigunta/hello-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server