Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
StaticAnalysisService.ts12.8 kB
// Copyright 2025 Chris Bunting // Brief: Core static analysis service for MCP Server // Scope: Orchestrates multi-language static analysis with various tools import { LanguageDetector } from './LanguageDetector.js'; import { ConfigParser } from './ConfigParser.js'; import { Logger } from '../utils/Logger.js'; import { AnalysisResult, AnalysisIssue, AnalysisMetrics, AnalysisOptions, SeverityLevel, IssueCategory, Language, FixSuggestion, } from '@mcp-code-analysis/shared-types'; import { glob } from 'fast-glob'; import { JavaScriptTypeScriptAnalyzer } from '../analyzers/JavaScriptTypeScriptAnalyzer.js'; import { PythonAnalyzer } from '../analyzers/PythonAnalyzer.js'; import { JavaAnalyzer } from '../analyzers/JavaAnalyzer.js'; import { CppAnalyzer } from '../analyzers/CppAnalyzer.js'; import { GoAnalyzer } from '../analyzers/GoAnalyzer.js'; import { RustAnalyzer } from '../analyzers/RustAnalyzer.js'; export interface AnalyzerInterface { analyzeFile(filePath: string, options?: AnalysisOptions): Promise<AnalysisResult>; analyzeProject(projectPath: string, patterns?: string[], excludePatterns?: string[], options?: AnalysisOptions): Promise<AnalysisResult[]>; getRules(configFile?: string): Promise<any>; configure(config: any): Promise<void>; } export class StaticAnalysisService { private languageDetector: LanguageDetector; private configParser: ConfigParser; private logger: Logger; private analyzers: Map<Language, AnalyzerInterface> = new Map(); constructor( languageDetector: LanguageDetector, configParser: ConfigParser, logger: Logger ) { this.languageDetector = languageDetector; this.configParser = configParser; this.logger = logger; this.initializeAnalyzers(); } private initializeAnalyzers(): void { // Initialize language-specific analyzers this.logger.info('Initializing analyzers for supported languages'); // Initialize JavaScript/TypeScript analyzer this.analyzers.set(Language.JAVASCRIPT, new JavaScriptTypeScriptAnalyzer(this.logger, this.configParser)); this.analyzers.set(Language.TYPESCRIPT, new JavaScriptTypeScriptAnalyzer(this.logger, this.configParser)); // Initialize Python analyzer this.analyzers.set(Language.PYTHON, new PythonAnalyzer(this.logger, this.configParser)); // Initialize Java analyzer this.analyzers.set(Language.JAVA, new JavaAnalyzer(this.logger, this.configParser)); // Initialize C/C++ analyzers this.analyzers.set(Language.C, new CppAnalyzer(this.logger, this.configParser)); this.analyzers.set(Language.CPP, new CppAnalyzer(this.logger, this.configParser)); // Initialize Go analyzer this.analyzers.set(Language.GO, new GoAnalyzer(this.logger, this.configParser)); // Initialize Rust analyzer this.analyzers.set(Language.RUST, new RustAnalyzer(this.logger, this.configParser)); this.logger.info(`Initialized ${this.analyzers.size} language analyzers`); } async analyzeFile(filePath: string, language?: string, options: AnalysisOptions = {}): Promise<AnalysisResult> { try { const detectedLanguage = language ? this.parseLanguage(language) : this.languageDetector.detectLanguage(filePath); this.logger.info(`Analyzing file: ${filePath} (${detectedLanguage})`); if (!this.analyzers.has(detectedLanguage)) { throw new Error(`Unsupported language: ${detectedLanguage}`); } const analyzer = this.analyzers.get(detectedLanguage)!; const result = await analyzer.analyzeFile(filePath, options); // Enhance result with additional metadata result.id = this.generateAnalysisId(); result.timestamp = new Date(); result.filePath = filePath; result.language = detectedLanguage; return result; } catch (error) { this.logger.error(`Error analyzing file ${filePath}:`, error); throw this.createAnalysisError(filePath, error); } } async analyzeProject( projectPath: string, filePatterns?: string[], excludePatterns?: string[], options: AnalysisOptions = {} ): Promise<AnalysisResult[]> { try { this.logger.info(`Analyzing project: ${projectPath}`); const files = await this.discoverProjectFiles(projectPath, filePatterns, excludePatterns); const results: AnalysisResult[] = []; for (const file of files) { try { const result = await this.analyzeFile(file, undefined, options); results.push(result); } catch (error) { this.logger.warn(`Failed to analyze file ${file}:`, error); // Continue with other files } } return results; } catch (error) { this.logger.error(`Error analyzing project ${projectPath}:`, error); throw error; } } async getRules(language: string, configFile?: string): Promise<any> { try { const detectedLanguage = this.parseLanguage(language); this.logger.info(`Getting rules for language: ${detectedLanguage}`); if (!this.analyzers.has(detectedLanguage)) { throw new Error(`Unsupported language: ${detectedLanguage}`); } const analyzer = this.analyzers.get(detectedLanguage)!; return await analyzer.getRules(configFile); } catch (error) { this.logger.error(`Error getting rules for ${language}:`, error); throw error; } } async configureAnalyzer(language: string, config: any): Promise<any> { try { const detectedLanguage = this.parseLanguage(language); this.logger.info(`Configuring analyzer for language: ${detectedLanguage}`); if (!this.analyzers.has(detectedLanguage)) { throw new Error(`Unsupported language: ${detectedLanguage}`); } const analyzer = this.analyzers.get(detectedLanguage)!; await analyzer.configure(config); return { success: true, message: `Analyzer configured for ${detectedLanguage}` }; } catch (error) { this.logger.error(`Error configuring analyzer for ${language}:`, error); throw error; } } async batchAnalyze(filePaths: string[], options: AnalysisOptions = {}): Promise<AnalysisResult[]> { try { this.logger.info(`Batch analyzing ${filePaths.length} files`); const results: AnalysisResult[] = []; const batchSize = 10; // Process files in batches to avoid overwhelming the system for (let i = 0; i < filePaths.length; i += batchSize) { const batch = filePaths.slice(i, i + batchSize); const batchPromises = batch.map(filePath => this.analyzeFile(filePath, undefined, options).catch(error => { this.logger.warn(`Failed to analyze file ${filePath}:`, error); return null; }) ); const batchResults = await Promise.all(batchPromises); results.push(...batchResults.filter((result): result is AnalysisResult => result !== null)); } return results; } catch (error) { this.logger.error('Error in batch analysis:', error); throw error; } } private async discoverProjectFiles( projectPath: string, includePatterns?: string[], excludePatterns?: string[] ): Promise<string[]> { const defaultPatterns = [ '**/*.{js,jsx,ts,tsx,py,java,c,cpp,go,rs}', '**/*.{h,hpp,cpp,cxx,cc}', ]; const defaultExcludePatterns = [ '**/node_modules/**', '**/dist/**', '**/build/**', '**/target/**', '**/.git/**', '**/venv/**', '**/env/**', '**/__pycache__/**', '**/*.min.js', '**/*.bundle.js', ]; const patterns = includePatterns ?? defaultPatterns; const exclude = excludePatterns ?? defaultExcludePatterns; try { const files = await glob(patterns, { cwd: projectPath, ignore: exclude, absolute: true, onlyFiles: true, }); return files.sort(); } catch (error) { this.logger.error('Error discovering project files:', error); throw error; } } private parseLanguage(language: string): Language { const normalizedLanguage = language.toLowerCase(); switch (normalizedLanguage) { case 'javascript': case 'js': return Language.JAVASCRIPT; case 'typescript': case 'ts': return Language.TYPESCRIPT; case 'python': case 'py': return Language.PYTHON; case 'java': return Language.JAVA; case 'c': return Language.C; case 'cpp': case 'c++': return Language.CPP; case 'go': return Language.GO; case 'rust': case 'rs': return Language.RUST; default: throw new Error(`Unsupported language: ${language}`); } } private generateAnalysisId(): string { return `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private createAnalysisError(filePath: string, error: any): Error { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return new Error(`Analysis failed for ${filePath}: ${errorMessage}`); } // Helper methods for creating standardized analysis results createAnalysisResult( filePath: string, language: Language, issues: AnalysisIssue[], metrics: AnalysisMetrics ): AnalysisResult { return { id: this.generateAnalysisId(), timestamp: new Date(), filePath, language, issues, metrics, severity: this.calculateOverallSeverity(issues), }; } createAnalysisIssue( ruleId: string, message: string, severity: SeverityLevel, line: number, column: number, category: IssueCategory, fix?: FixSuggestion, tags: string[] = [] ): AnalysisIssue { return { id: this.generateIssueId(), ruleId, message, severity, line, column, category, fix, tags, }; } createAnalysisMetrics( linesOfCode: number, commentLines: number, cyclomaticComplexity: number = 0, cognitiveComplexity: number = 0, maintainabilityIndex: number = 0 ): AnalysisMetrics { return { linesOfCode, commentLines, cyclomaticComplexity, cognitiveComplexity, maintainabilityIndex, }; } private calculateOverallSeverity(issues: AnalysisIssue[]): SeverityLevel { if (issues.length === 0) { return SeverityLevel.INFO; } const hasErrors = issues.some(issue => issue.severity === SeverityLevel.ERROR); const hasWarnings = issues.some(issue => issue.severity === SeverityLevel.WARNING); if (hasErrors) { return SeverityLevel.ERROR; } else if (hasWarnings) { return SeverityLevel.WARNING; } else { return SeverityLevel.INFO; } } private generateIssueId(): string { return `issue_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // Public utility methods getSupportedLanguages(): Language[] { return this.languageDetector.getSupportedLanguages(); } isLanguageSupported(language: string): boolean { try { const parsedLanguage = this.parseLanguage(language); return this.languageDetector.isLanguageSupported(parsedLanguage); } catch { return false; } } async getProjectConfig(projectPath: string, language?: Language): Promise<any> { try { const configFiles = await this.configParser.findConfigFiles(projectPath, language); return this.configParser.mergeConfigs(configFiles); } catch (error) { this.logger.error('Error getting project config:', error); return {}; } } // Statistics and reporting generateAnalysisReport(results: AnalysisResult[]): any { const totalFiles = results.length; const totalIssues = results.reduce((sum, result) => sum + result.issues.length, 0); const issuesBySeverity = { error: 0, warning: 0, info: 0, hint: 0, }; const issuesByCategory = { syntax: 0, style: 0, security: 0, performance: 0, maintainability: 0, accessibility: 0, }; const issuesByLanguage: Record<string, number> = {}; results.forEach(result => { result.issues.forEach(issue => { issuesBySeverity[issue.severity as keyof typeof issuesBySeverity]++; issuesByCategory[issue.category as keyof typeof issuesByCategory]++; issuesByLanguage[result.language] = (issuesByLanguage[result.language] || 0) + 1; }); }); return { summary: { totalFiles, totalIssues, averageIssuesPerFile: totalFiles > 0 ? (totalIssues / totalFiles).toFixed(2) : 0, }, issuesBySeverity, issuesByCategory, issuesByLanguage, timestamp: new Date().toISOString(), }; } }

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/cbunting99/mcp-code-analysis-server'

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