Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
DependencyAnalyzer.ts26.2 kB
// Copyright 2025 Chris Bunting // Brief: Main dependency analysis service that orchestrates all other services // Scope: Provides comprehensive dependency analysis functionality import { DependencyInfo, DependencyOptions, PackageManager, SeverityLevel, DependencyType } from '@mcp-code-analysis/shared-types'; import { PackageManagerDetector } from './PackageManagerDetector.js'; import { DependencyGraphBuilder, DependencyGraph } from './DependencyGraphBuilder.js'; import { VersionResolver, UpdateInfo, VersionConflict } from './VersionResolver.js'; import { SecurityAuditor, SecurityAudit } from './SecurityAuditor.js'; import { Logger } from '../utils/Logger.js'; export interface AnalysisResult { projectPath: string; packageManager: PackageManager; dependencies: DependencyInfo[]; graph: DependencyGraph; metrics: { totalDependencies: number; directDependencies: number; transitiveDependencies: number; maxDepth: number; averageDepth: number; packageCount: number; }; conflicts: VersionConflict[]; circularDependencies: string[][]; securityAudits: SecurityAudit[]; outdatedPackages: UpdateInfo[]; recommendations: string[]; analysisTime: number; } export interface AlternativePackage { name: string; description: string; packageManager: PackageManager; downloads: number; stars: number; lastUpdated: string; score: number; features: string[]; } export class DependencyAnalyzer { private packageManagerDetector: PackageManagerDetector; private dependencyGraphBuilder: DependencyGraphBuilder; private versionResolver: VersionResolver; private securityAuditor: SecurityAuditor; private logger: Logger; constructor( packageManagerDetector: PackageManagerDetector, dependencyGraphBuilder: DependencyGraphBuilder, versionResolver: VersionResolver, securityAuditor: SecurityAuditor, logger: Logger ) { this.packageManagerDetector = packageManagerDetector; this.dependencyGraphBuilder = dependencyGraphBuilder; this.versionResolver = versionResolver; this.securityAuditor = securityAuditor; this.logger = logger; } async analyzeProject(projectPath: string, options: DependencyOptions = {}): Promise<AnalysisResult> { const startTime = Date.now(); this.logger.info(`Starting dependency analysis for: ${projectPath}`); try { // Detect package manager const packageManager = await this.packageManagerDetector.getPrimaryPackageManager(projectPath); if (!packageManager) { throw new Error('No package manager detected in the project'); } this.logger.debug(`Detected package manager: ${packageManager}`); // Parse dependencies based on package manager const dependencies = await this.parseDependencies(projectPath, packageManager, options); this.logger.debug(`Found ${dependencies.length} dependencies`); // Get project info (name and version) const projectInfo = await this.getProjectInfo(projectPath, packageManager); // Build dependency graph const graph = await this.dependencyGraphBuilder.buildGraph( projectInfo.name, projectInfo.version, dependencies, packageManager ); // Calculate metrics const metrics = this.dependencyGraphBuilder.calculateDependencyMetrics(graph); // Find version conflicts const conflicts = await this.versionResolver.findVersionConflicts( this.extractDependencyInfo(dependencies) ); // Find circular dependencies const circularDependencies = this.dependencyGraphBuilder.findCircularDependencies(graph); // Perform security audits const securityAudits = await this.performSecurityAudits(dependencies, packageManager); // Check for outdated packages const outdatedPackages = await this.checkForOutdatedPackages(dependencies, packageManager); // Generate recommendations const recommendations = await this.generateRecommendations({ dependencies, metrics, conflicts, circularDependencies, securityAudits, outdatedPackages, packageManager, }); const analysisTime = Date.now() - startTime; const result: AnalysisResult = { projectPath, packageManager, dependencies, graph, metrics, conflicts, circularDependencies, securityAudits, outdatedPackages, recommendations, analysisTime, }; this.logger.info(`Dependency analysis completed in ${analysisTime}ms`); return result; } catch (error) { this.logger.error('Error analyzing project:', error); throw error; } } private async parseDependencies( projectPath: string, packageManager: PackageManager, options: DependencyOptions ): Promise<DependencyInfo[]> { switch (packageManager) { case PackageManager.NPM: case PackageManager.YARN: return this.parseNpmDependencies(projectPath, options); case PackageManager.PIP: return this.parsePipDependencies(projectPath, options); case PackageManager.CARGO: return this.parseCargoDependencies(projectPath, options); case PackageManager.MAVEN: return this.parseMavenDependencies(projectPath, options); case PackageManager.GRADLE: return this.parseGradleDependencies(projectPath, options); case PackageManager.GO: return this.parseGoDependencies(projectPath, options); default: throw new Error(`Unsupported package manager: ${packageManager}`); } } private async parseNpmDependencies(projectPath: string, options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); const packageJsonPath = path.join(projectPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { throw new Error('package.json not found'); } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies: DependencyInfo[] = []; const processDeps = (deps: Record<string, string>, type: DependencyType) => { if (!deps) return; for (const [name, version] of Object.entries(deps)) { dependencies.push({ name, version, type, manager: PackageManager.NPM, licenses: [], vulnerabilities: [], dependencies: [], }); } }; if (options.includeDev !== false) { processDeps(packageJson.devDependencies, DependencyType.DEVELOPMENT); } if (options.includePeer !== false) { processDeps(packageJson.peerDependencies, DependencyType.PEER); } if (options.includeOptional !== false) { processDeps(packageJson.optionalDependencies, DependencyType.OPTIONAL); } processDeps(packageJson.dependencies, DependencyType.PRODUCTION); return dependencies; } private async parsePipDependencies(projectPath: string, _options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); const dependencies: DependencyInfo[] = []; // Try requirements.txt first const requirementsPath = path.join(projectPath, 'requirements.txt'); if (fs.existsSync(requirementsPath)) { const content = fs.readFileSync(requirementsPath, 'utf8'); const lines = content.split('\n').filter(line => line.trim() && !line.startsWith('#')); for (const line of lines) { const match = line.match(/^([a-zA-Z0-9\-_.]+)([><=!~]+.*)?$/); if (match) { const name = match[1]; const version = match[2] || '*'; dependencies.push({ name, version, type: DependencyType.PRODUCTION, manager: PackageManager.PIP, licenses: [], vulnerabilities: [], dependencies: [], }); } } } // Try setup.py const setupPyPath = path.join(projectPath, 'setup.py'); if (fs.existsSync(setupPyPath) && dependencies.length === 0) { // This is a simplified parser - in reality, you'd need to execute setup.py or parse it more carefully dependencies.push({ name: 'python-package', version: '*', type: DependencyType.PRODUCTION, manager: PackageManager.PIP, licenses: [], vulnerabilities: [], dependencies: [], }); } return dependencies; } private async parseCargoDependencies(projectPath: string, options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); const toml = await import('toml'); const cargoTomlPath = path.join(projectPath, 'Cargo.toml'); if (!fs.existsSync(cargoTomlPath)) { throw new Error('Cargo.toml not found'); } const cargoToml = toml.parse(fs.readFileSync(cargoTomlPath, 'utf8')); const dependencies: DependencyInfo[] = []; const processDeps = (deps: Record<string, any>, type: DependencyType) => { if (!deps) return; for (const [name, versionSpec] of Object.entries(deps)) { const version = typeof versionSpec === 'string' ? versionSpec : versionSpec.version || '*'; dependencies.push({ name, version, type, manager: PackageManager.CARGO, licenses: [], vulnerabilities: [], dependencies: [], }); } }; processDeps(cargoToml.dependencies, DependencyType.PRODUCTION); if (options.includeDev !== false) { processDeps(cargoToml['dev-dependencies'], DependencyType.DEVELOPMENT); } return dependencies; } private async parseMavenDependencies(projectPath: string, _options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); const xml2js = await import('xml2js'); const pomXmlPath = path.join(projectPath, 'pom.xml'); if (!fs.existsSync(pomXmlPath)) { throw new Error('pom.xml not found'); } const pomContent = fs.readFileSync(pomXmlPath, 'utf8'); const pom = await xml2js.parseStringPromise(pomContent); const dependencies: DependencyInfo[] = []; const deps = pom.project?.dependencies?.dependency || []; for (const dep of deps) { dependencies.push({ name: `${dep.groupId[0]}:${dep.artifactId[0]}`, version: dep.version?.[0] || '*', type: DependencyType.PRODUCTION, manager: PackageManager.MAVEN, licenses: [], vulnerabilities: [], dependencies: [], }); } return dependencies; } private async parseGradleDependencies(projectPath: string, _options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); // This is a simplified implementation // In reality, you'd need to parse Gradle build files which can be complex const buildGradlePath = path.join(projectPath, 'build.gradle'); if (!fs.existsSync(buildGradlePath)) { throw new Error('build.gradle not found'); } const content = fs.readFileSync(buildGradlePath, 'utf8'); const dependencies: DependencyInfo[] = []; // Simple regex-based parsing for demonstration const depRegex = /(?:implementation|compile|api|testImplementation)\s+['"]([^'"]+)['"]/g; let match; while ((match = depRegex.exec(content)) !== null) { const depSpec = match[1]; const parts = depSpec.split(':'); if (parts.length >= 2) { const name = parts.length === 2 ? parts[0] : `${parts[0]}:${parts[1]}`; const version = parts.length === 2 ? parts[1] : (parts[2] || '*'); dependencies.push({ name, version, type: DependencyType.PRODUCTION, manager: PackageManager.GRADLE, licenses: [], vulnerabilities: [], dependencies: [], }); } } return dependencies; } private async parseGoDependencies(projectPath: string, _options: DependencyOptions): Promise<DependencyInfo[]> { const fs = await import('fs'); const path = await import('path'); const goModPath = path.join(projectPath, 'go.mod'); if (!fs.existsSync(goModPath)) { throw new Error('go.mod not found'); } const content = fs.readFileSync(goModPath, 'utf8'); const dependencies: DependencyInfo[] = []; const lines = content.split('\n'); let inRequireBlock = false; for (const line of lines) { const trimmed = line.trim(); if (trimmed === 'require (') { inRequireBlock = true; continue; } else if (trimmed === ')' && inRequireBlock) { inRequireBlock = false; continue; } if (inRequireBlock || trimmed.startsWith('require ')) { const match = trimmed.match(/(?:require\s+)?([^\s]+)\s+([^\s]+)/); if (match) { const name = match[1]; const version = match[2]; dependencies.push({ name, version, type: DependencyType.PRODUCTION, manager: PackageManager.GO, licenses: [], vulnerabilities: [], dependencies: [], }); } } } return dependencies; } private async getProjectInfo(projectPath: string, packageManager: PackageManager): Promise<{ name: string; version: string }> { switch (packageManager) { case PackageManager.NPM: case PackageManager.YARN: const fs = await import('fs'); const path = await import('path'); const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return { name: packageJson.name || 'unknown', version: packageJson.version || '1.0.0', }; default: return { name: 'unknown', version: '1.0.0', }; } } private extractDependencyInfo(dependencies: DependencyInfo[]): Array<{ name: string; version: string; constraint?: string }> { return dependencies.map(dep => ({ name: dep.name, version: dep.version, constraint: dep.version, // For now, use version as constraint })); } private async performSecurityAudits(dependencies: DependencyInfo[], packageManager: PackageManager): Promise<SecurityAudit[]> { const audits: SecurityAudit[] = []; for (const dep of dependencies) { try { const audit = await this.securityAuditor.auditPackage(dep.name, dep.version, packageManager); audits.push(audit); } catch (error) { this.logger.warn(`Failed to audit ${dep.name}:`, error); } } return audits; } private async checkForOutdatedPackages(dependencies: DependencyInfo[], packageManager: PackageManager): Promise<UpdateInfo[]> { const outdatedPackages: UpdateInfo[] = []; for (const dep of dependencies) { try { // Mock registry versions - in reality, you'd query the actual package registry const mockRegistryVersions = await this.getMockRegistryVersions(dep.name, packageManager); const updateInfo = await this.versionResolver.checkForUpdates( dep.name, dep.version, mockRegistryVersions ); if (updateInfo && updateInfo.semverDiff !== 'none') { outdatedPackages.push(updateInfo); } } catch (error) { this.logger.warn(`Failed to check updates for ${dep.name}:`, error); } } return outdatedPackages; } private async getMockRegistryVersions(_packageName: string, _packageManager: PackageManager): Promise<string[]> { // Mock registry versions for demonstration // In reality, you'd query the actual package registry APIs const baseVersions = ['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0', '2.2.0']; return baseVersions.map(v => v); } private async generateRecommendations(context: { dependencies: DependencyInfo[]; metrics: any; conflicts: VersionConflict[]; circularDependencies: string[][]; securityAudits: SecurityAudit[]; outdatedPackages: UpdateInfo[]; packageManager: PackageManager; }): Promise<string[]> { const recommendations: string[] = []; // Security recommendations const vulnerableAudits = context.securityAudits.filter(audit => audit.vulnerabilities.length > 0); if (vulnerableAudits.length > 0) { recommendations.push( `🚨 ${vulnerableAudits.length} packages have security vulnerabilities. Update them immediately.` ); } // Outdated packages recommendations if (context.outdatedPackages.length > 0) { const criticalUpdates = context.outdatedPackages.filter(pkg => pkg.semverDiff === 'major'); const minorUpdates = context.outdatedPackages.filter(pkg => pkg.semverDiff === 'minor'); if (criticalUpdates.length > 0) { recommendations.push( `⚠️ ${criticalUpdates.length} packages have major updates available. Review breaking changes carefully.` ); } if (minorUpdates.length > 0) { recommendations.push( `📦 ${minorUpdates.length} packages have minor updates available. Consider updating them.` ); } } // Version conflict recommendations if (context.conflicts.length > 0) { recommendations.push( `🔀 ${context.conflicts.length} packages have version conflicts. Resolve them to ensure stability.` ); } // Circular dependency recommendations if (context.circularDependencies.length > 0) { recommendations.push( `🔄 ${context.circularDependencies.length} circular dependencies detected. Refactor to improve build performance.` ); } // Dependency count recommendations if (context.metrics.totalDependencies > 100) { recommendations.push( `📊 High dependency count (${context.metrics.totalDependencies}). Consider auditing and removing unused dependencies.` ); } // Tree depth recommendations if (context.metrics.maxDepth > 8) { recommendations.push( `🌳 Deep dependency tree (${context.metrics.maxDepth} levels). Consider flattening dependencies where possible.` ); } // Package manager specific recommendations switch (context.packageManager) { case PackageManager.NPM: case PackageManager.YARN: recommendations.push('Run `npm audit` or `yarn audit` regularly to check for security issues.'); recommendations.push('Consider using `npm outdated` or `yarn outdated` to check for updates.'); break; case PackageManager.PIP: recommendations.push('Use `pip-audit` or `safety check` for security scanning.'); recommendations.push('Consider using `pip list --outdated` to check for updates.'); break; case PackageManager.CARGO: recommendations.push('Use `cargo audit` for security vulnerability scanning.'); recommendations.push('Use `cargo update` to update dependencies.'); break; } // General recommendations recommendations.push('Implement dependency management in your CI/CD pipeline.'); recommendations.push('Regularly review and update dependencies to patch security vulnerabilities.'); recommendations.push('Consider using dependency locking for reproducible builds.'); return recommendations; } async checkUpdates(projectPath: string, packageName?: string): Promise<UpdateInfo[]> { this.logger.info(`Checking for updates in: ${projectPath}${packageName ? ` for package: ${packageName}` : ''}`); try { const packageManager = await this.packageManagerDetector.getPrimaryPackageManager(projectPath); if (!packageManager) { throw new Error('No package manager detected'); } const dependencies = await this.parseDependencies(projectPath, packageManager, {}); if (packageName) { // Check updates for specific package const dep = dependencies.find(d => d.name === packageName); if (!dep) { throw new Error(`Package ${packageName} not found in dependencies`); } const mockRegistryVersions = await this.getMockRegistryVersions(dep.name, packageManager); const updateInfo = await this.versionResolver.checkForUpdates( dep.name, dep.version, mockRegistryVersions ); return updateInfo ? [updateInfo] : []; } else { // Check updates for all packages const outdatedPackages = await this.checkForOutdatedPackages(dependencies, packageManager); return outdatedPackages; } } catch (error) { this.logger.error('Error checking for updates:', error); throw error; } } async findConflicts(projectPath: string): Promise<VersionConflict[]> { this.logger.info(`Finding version conflicts in: ${projectPath}`); try { const packageManager = await this.packageManagerDetector.getPrimaryPackageManager(projectPath); if (!packageManager) { throw new Error('No package manager detected'); } const dependencies = await this.parseDependencies(projectPath, packageManager, {}); const conflicts = await this.versionResolver.findVersionConflicts( this.extractDependencyInfo(dependencies) ); return conflicts; } catch (error) { this.logger.error('Error finding conflicts:', error); throw error; } } async suggestAlternatives(packageName: string, packageManager?: PackageManager): Promise<AlternativePackage[]> { this.logger.info(`Suggesting alternatives for: ${packageName}`); try { // This is a mock implementation // In reality, you'd query package registry APIs to find alternatives const alternatives: AlternativePackage[] = []; // Mock alternatives for common packages const mockAlternatives: Record<string, AlternativePackage[]> = { 'lodash': [ { name: 'underscore', description: 'JavaScript utility library', packageManager: PackageManager.NPM, downloads: 5000000, stars: 26000, lastUpdated: '2024-01-15', score: 85, features: ['functional programming', 'utility functions'], }, { name: 'ramda', description: 'Functional programming library', packageManager: PackageManager.NPM, downloads: 800000, stars: 22000, lastUpdated: '2024-01-10', score: 90, features: ['functional programming', 'immutable operations'], }, ], 'express': [ { name: 'fastify', description: 'Fast and low overhead web framework', packageManager: PackageManager.NPM, downloads: 2000000, stars: 28000, lastUpdated: '2024-01-20', score: 95, features: ['high performance', 'plugin system', 'schema validation'], }, { name: 'koa', description: 'Expressive middleware for node.js', packageManager: PackageManager.NPM, downloads: 1500000, stars: 34000, lastUpdated: '2024-01-18', score: 88, features: ['async/await', 'middleware', 'context'], }, ], }; const packageAlternatives = mockAlternatives[packageName.toLowerCase()]; if (packageAlternatives) { alternatives.push(...packageAlternatives); } else { // Generate generic alternative alternatives.push({ name: `${packageName}-alternative`, description: `Alternative to ${packageName}`, packageManager: packageManager || PackageManager.NPM, downloads: 100000, stars: 1000, lastUpdated: '2024-01-01', score: 70, features: ['alternative implementation'], }); } // Sort by score alternatives.sort((a, b) => b.score - a.score); return alternatives; } catch (error) { this.logger.error('Error suggesting alternatives:', error); throw error; } } async securityAudit(projectPath: string, severity: SeverityLevel = SeverityLevel.WARNING): Promise<any> { this.logger.info(`Performing security audit for: ${projectPath}`); try { const packageManager = await this.packageManagerDetector.getPrimaryPackageManager(projectPath); if (!packageManager) { throw new Error('No package manager detected'); } const dependencies = await this.parseDependencies(projectPath, packageManager, {}); const audits = await this.performSecurityAudits(dependencies, packageManager); // Filter by severity const filteredAudits = audits.filter(audit => audit.vulnerabilities.some(vuln => this.compareSeverity(vuln.severity, severity) >= 0 ) ); // Generate security report const securityReport = await this.securityAuditor.generateSecurityReport(filteredAudits); return { audits: filteredAudits, report: securityReport, severity, }; } catch (error) { this.logger.error('Error performing security audit:', error); throw error; } } private compareSeverity(severity1: SeverityLevel, severity2: SeverityLevel): number { const severityOrder = { [SeverityLevel.ERROR]: 4, [SeverityLevel.WARNING]: 3, [SeverityLevel.INFO]: 2, [SeverityLevel.HINT]: 1, }; return severityOrder[severity1] - severityOrder[severity2]; } clearCache(): void { this.versionResolver.clearCache(); this.securityAuditor.clearCache(); this.logger.debug('Dependency analyzer cache cleared'); } }

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