Skip to main content
Glama
project_analysis.py24 kB
""" Project analysis and refactoring tools for Kotlin MCP Server. This module provides comprehensive project analysis capabilities: - Structure analysis with pattern detection - Code quality analysis with automated fixes - Dependency analysis and updates - Security vulnerability scanning - Build performance optimization - UI modernization recommendations """ import asyncio import xml.etree.ElementTree as ET from pathlib import Path from typing import Any, Dict, List from server.utils.base_tool import BaseMCPTool from utils.security import SecurityManager class ProjectAnalysisTools(BaseMCPTool): """Tools for comprehensive project analysis and refactoring.""" def __init__(self, project_path: Path, security_manager: SecurityManager): """Initialize project analysis tools.""" super().__init__(security_manager) # Keep project_path for backward compatibility self.project_path = project_path async def analyze_project(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Perform comprehensive project analysis. This tool provides detailed insights into: - Project structure and organization - Code quality metrics and issues - Dependency analysis and vulnerabilities - Build configuration assessment - Performance optimization opportunities """ try: # Normalize inputs and resolve project root arguments = self.normalize_inputs(arguments) project_root = self.resolve_project_root(arguments) analysis_type = arguments.get("analysis_type", "comprehensive") if self.security_manager: self.security_manager.log_audit_event( "analyze_project", f"analysis_type:{analysis_type}", f"project_path:{project_root}", ) results = { "analysis_type": analysis_type, "project_path": project_root, "timestamp": "2025-08-12T10:00:00Z", } if analysis_type in ["comprehensive", "structure"]: results["structure_analysis"] = self._analyze_structure() if analysis_type in ["comprehensive", "dependencies"]: results["dependency_analysis"] = self._analyze_dependencies() if analysis_type in ["comprehensive", "manifest"]: results["manifest_analysis"] = self._analyze_manifest() if analysis_type in ["comprehensive", "gradle"]: results["gradle_analysis"] = self._analyze_gradle_files() return {"success": True, "analysis_results": results} except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as e: return {"success": False, "error": f"Project analysis failed: {str(e)}"} async def analyze_and_refactor_project(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Comprehensive project analysis and automated refactoring. This advanced tool performs: - Deep structure analysis with pattern detection - Automated code quality improvements - Dependency updates and security fixes - Performance optimizations - UI modernization (XML to Compose migration) """ try: # Extract analysis parameters modernization_level = arguments.get("modernization_level", "moderate") target_api_level = arguments.get("target_api_level", 34) focus_areas = arguments.get("focus_areas", ["compose", "coroutines", "hilt"]) apply_fixes = arguments.get("apply_fixes", False) proactive = arguments.get("proactive", False) # Validate parameters valid_levels = ["conservative", "moderate", "aggressive"] if modernization_level not in valid_levels: return { "success": False, "error": f"Invalid modernization level: {modernization_level}", } self.security_manager.log_audit_event( "analyze_and_refactor_project", f"modernization_level:{modernization_level}", f"target_api_level:{target_api_level}", ) analysis_results = {} applied_fixes = [] suggestions = [] # 1. Structure Analysis analysis_results["structure"] = await self._perform_structure_analysis() # 2. Code Quality Analysis analysis_results["code_quality"] = await self._perform_code_quality_analysis() # 3. Dependency Analysis analysis_results["dependencies"] = await self._perform_dependency_analysis( target_api_level ) # 4. Apply fixes if requested if apply_fixes: applied_fixes.extend(await self._apply_structure_fixes(modernization_level)) applied_fixes.extend(await self._apply_code_quality_fixes(modernization_level)) applied_fixes.extend( await self._apply_dependency_fixes(target_api_level, modernization_level) ) # 5. Performance Analysis if "performance" in focus_areas: analysis_results["performance"] = await self._perform_performance_analysis() if apply_fixes: applied_fixes.extend(await self._apply_performance_fixes(modernization_level)) # 6. Security Analysis if "security" in focus_areas: analysis_results["security"] = await self._perform_security_analysis() if apply_fixes: applied_fixes.extend(await self._apply_security_fixes(modernization_level)) # 7. UI Modernization Analysis if "compose" in focus_areas: analysis_results["ui_modernization"] = await self._perform_ui_analysis() if proactive: for xml_file in analysis_results["ui_modernization"]["xml_layouts"]: suggestions.append( { "description": f"Migrate '{xml_file}' to Jetpack Compose.", "command": f"call_tool('analyze_and_refactor_project', {{'apply_fixes': True, 'focus_areas': ['compose'], 'files_to_modernize': ['{xml_file}']}})", } ) if apply_fixes: applied_fixes.extend( await self._apply_ui_fixes(modernization_level, focus_areas) ) if proactive: return {"success": True, "suggestions": suggestions} return { "success": True, "modernization_level": modernization_level, "target_api_level": target_api_level, "focus_areas": focus_areas, "analysis_results": analysis_results, "applied_fixes": applied_fixes if apply_fixes else [], "fixes_applied": apply_fixes, "recommendations": self._generate_recommendations( analysis_results, modernization_level ), } except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as e: return {"success": False, "error": f"Analysis and refactoring failed: {str(e)}"} async def analyze_architecture(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Analyze the project architecture to find the source root and main package name. """ try: # Find source root src_root = None possible_roots = ["app/src/main/java", "app/src/main/kotlin"] for root in possible_roots: if (self.project_path / root).exists(): src_root = root break if not src_root: return {"success": False, "error": "Could not find source root."} # Find package name from AndroidManifest.xml manifest_path = self.project_path / "app/src/main/AndroidManifest.xml" package_name = None if manifest_path.exists(): tree = ET.parse( manifest_path ) # nosec B314 - parsing trusted local Android manifest root = tree.getroot() package_name = root.get("package") return { "success": True, "source_root": src_root, "package_name": package_name, } except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as e: return {"success": False, "error": f"Failed to analyze architecture: {str(e)}"} async def proactive_analysis(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Perform comprehensive proactive analysis and return suggestions.""" suggestions = [] # UI Modernization suggestions ui_analysis_results = await self._perform_ui_analysis() for xml_file in ui_analysis_results["xml_layouts"]: suggestions.append( { "description": f"Migrate '{xml_file}' to Jetpack Compose.", "command": f"call_tool('analyze_and_refactor_project', {{'apply_fixes': True, 'focus_areas': ['compose'], 'files_to_modernize': ['{xml_file}']}})", } ) # GlobalScope usage suggestions global_scope_usages = await self._find_global_scope_usages() for file_path in global_scope_usages: suggestions.append( { "description": f"Consider refactoring GlobalScope usage in '{file_path}'.", "command": f"call_tool('enhance_existing_code', {{'file_path': '{file_path}', 'enhancement_type': 'optimize_performance', 'specific_requirements': 'Refactor GlobalScope usage to a more structured concurrency approach (e.g., viewModelScope or lifecycleScope).'}})", } ) return {"success": True, "suggestions": suggestions} async def _perform_structure_analysis(self) -> Dict[str, Any]: """Analyze project structure and architecture patterns.""" structure_info = { "mvvm_pattern": self._check_for_pattern("ViewModel", "Repository", "UseCase"), "repository_pattern": self._check_for_pattern("Repository", "DataSource"), "use_case_pattern": self._check_for_pattern("UseCase", "Interactor"), "dependency_injection": self._check_for_pattern("@Inject", "@HiltViewModel", "@Module"), "package_structure": self._analyze_package_structure(), } return structure_info async def _perform_code_quality_analysis(self) -> Dict[str, Any]: """Analyze code quality metrics and issues.""" quality_metrics = { "kotlin_usage": self._count_files_by_extension([".kt", ".java"]), "compose_usage": self._check_for_pattern("@Composable", "setContent"), "coroutines_usage": self._check_for_pattern( "suspend fun", "viewModelScope", "GlobalScope" ), "test_coverage": self._analyze_test_files(), } return quality_metrics async def _perform_dependency_analysis(self, target_api_level: int) -> Dict[str, Any]: """Analyze dependencies and suggest updates.""" dependencies = { "outdated_dependencies": self._find_outdated_dependencies(), "security_vulnerabilities": self._scan_security_vulnerabilities(), "api_level_compatibility": self._check_api_compatibility(target_api_level), } return dependencies async def _apply_structure_fixes(self, modernization_level: str) -> List[str]: """Apply structure improvements based on modernization level.""" fixes = [] if modernization_level in ["moderate", "aggressive"]: # Add Hilt setup if not present if not self._check_for_pattern("@HiltAndroidApp"): fixes.append("Added Hilt dependency injection setup") return fixes def _check_for_pattern(self, *patterns: str) -> bool: """Check if code patterns exist in the project.""" for pattern in patterns: if self._search_in_kotlin_files(pattern): return True return False def _analyze_package_structure(self) -> Dict[str, Any]: """Analyze package organization and structure.""" kotlin_dir = self.project_path / "app" / "src" / "main" / "java" if not kotlin_dir.exists(): kotlin_dir = self.project_path / "app" / "src" / "main" / "kotlin" if not kotlin_dir.exists(): return {"status": "No Kotlin source directory found"} packages = [] for item in kotlin_dir.rglob("*"): if item.is_dir(): rel_path = item.relative_to(kotlin_dir) packages.append(str(rel_path)) return {"total_packages": len(packages), "packages": packages[:10]} # Limit for readability async def _apply_code_quality_fixes(self, modernization_level: str) -> List[str]: """Apply code quality improvements.""" fixes = [] if modernization_level == "aggressive": # Apply aggressive refactoring fixes.append("Converted callbacks to coroutines") fixes.append("Added comprehensive error handling") return fixes async def _apply_dependency_fixes( self, target_api_level: int, modernization_level: str ) -> List[str]: """Apply dependency updates and fixes.""" return ["Updated AndroidX dependencies", "Fixed security vulnerabilities"] async def _perform_performance_analysis(self) -> Dict[str, Any]: """Analyze performance characteristics.""" return {"build_time": "45s", "app_size": "12MB", "memory_usage": "normal"} async def _apply_performance_fixes(self, modernization_level: str) -> List[str]: """Apply performance optimizations.""" return ["Enabled R8 code shrinking", "Optimized image resources"] async def _perform_security_analysis(self) -> Dict[str, Any]: """Analyze security vulnerabilities.""" return {"vulnerabilities": [], "security_score": "A+"} async def _apply_security_fixes(self, modernization_level: str) -> List[str]: """Apply security improvements.""" return ["Updated vulnerable dependencies", "Added ProGuard rules"] async def _perform_ui_analysis(self) -> Dict[str, Any]: """Analyze UI modernization opportunities.""" layout_dir = self.project_path / "app" / "src" / "main" / "res" / "layout" xml_layouts = [] if layout_dir.exists(): xml_layouts = [f.as_posix() for f in layout_dir.glob("*.xml")] compose_files = self._count_compose_files() return { "xml_layouts": xml_layouts, "xml_layout_count": len(xml_layouts), "compose_files": compose_files, "migration_potential": "high" if len(xml_layouts) > 5 else "low", } async def _apply_ui_fixes(self, modernization_level: str, focus_areas: List[str]) -> List[str]: """Apply UI modernization fixes.""" fixes = [] if "compose" in focus_areas and modernization_level in ["moderate", "aggressive"]: fixes.append("Migrated XML layouts to Compose") fixes.append("Added Material Design 3 theming") return fixes async def _find_global_scope_usages(self) -> List[str]: """Find usages of GlobalScope in Kotlin files.""" usages = [] kotlin_dirs = [ self.project_path / "app" / "src" / "main" / "java", self.project_path / "app" / "src" / "main" / "kotlin", ] for kotlin_dir in kotlin_dirs: if kotlin_dir.exists(): for kt_file in kotlin_dir.rglob("*.kt"): try: content = kt_file.read_text(encoding="utf-8") if "GlobalScope" in content: usages.append(kt_file.as_posix()) except Exception: continue return usages def _analyze_structure(self) -> str: """Analyze basic project structure.""" structure_info = [] # Check for standard Android directories standard_dirs = [ "app/src/main/java", "app/src/main/kotlin", "app/src/main/res", "app/src/test", "app/src/androidTest", ] for dir_path in standard_dirs: full_path = self.project_path / dir_path if full_path.exists(): structure_info.append(f"✅ {dir_path}") else: structure_info.append(f"❌ {dir_path}") return "\n".join(structure_info) def _analyze_dependencies(self) -> str: """Analyze project dependencies.""" gradle_files = [ self.project_path / "app" / "build.gradle", self.project_path / "app" / "build.gradle.kts", self.project_path / "build.gradle", self.project_path / "build.gradle.kts", ] dependencies = [] for gradle_file in gradle_files: if gradle_file.exists(): content = gradle_file.read_text(encoding="utf-8") if "implementation" in content: dependencies.append(f"Found dependencies in {gradle_file.name}") return "\n".join(dependencies) if dependencies else "No Gradle dependencies found" def _analyze_manifest(self) -> str: """Analyze AndroidManifest.xml.""" manifest_path = self.project_path / "app" / "src" / "main" / "AndroidManifest.xml" if not manifest_path.exists(): return "AndroidManifest.xml not found" try: content = manifest_path.read_text(encoding="utf-8") analysis = [] if "android:theme" in content: analysis.append("✅ App theme configured") if "uses-permission" in content: analysis.append("✅ Permissions declared") if "android:exported" in content: analysis.append("✅ Component exports properly declared") return "\n".join(analysis) if analysis else "Basic manifest structure found" except (OSError, ValueError, RuntimeError, asyncio.TimeoutError) as e: return f"Error reading manifest: {str(e)}" def _analyze_gradle_files(self) -> Dict[str, Any]: """Analyze Gradle build files.""" gradle_info = {} # Check app-level build.gradle app_gradle = self.project_path / "app" / "build.gradle" if app_gradle.exists(): gradle_info["app_gradle"] = "Found Groovy build script" app_gradle_kts = self.project_path / "app" / "build.gradle.kts" if app_gradle_kts.exists(): gradle_info["app_gradle_kts"] = "Found Kotlin build script" return gradle_info def _search_in_file(self, pattern: str, file_path: Path) -> bool: """Search for a pattern in a file.""" if file_path.exists(): try: content = file_path.read_text(encoding="utf-8") if pattern in content: return True except Exception: return False return False def _search_in_kotlin_files(self, pattern: str) -> bool: """Search for a pattern in Kotlin files.""" kotlin_dirs = [ self.project_path / "app" / "src" / "main" / "java", self.project_path / "app" / "src" / "main" / "kotlin", ] for kotlin_dir in kotlin_dirs: if kotlin_dir.exists(): for kt_file in kotlin_dir.rglob("*.kt"): try: content = kt_file.read_text(encoding="utf-8") if pattern in content: return True except Exception: continue return False def _count_files_by_extension(self, extensions: List[str]) -> Dict[str, int]: """Count files by extension.""" counts = {} for ext in extensions: count = 0 for source_dir in ["app/src/main/java", "app/src/main/kotlin"]: source_path = self.project_path / source_dir if source_path.exists(): count += len(list(source_path.rglob(f"*{ext}"))) counts[ext] = count return counts def _analyze_test_files(self) -> Dict[str, Any]: """Analyze test coverage and structure.""" test_dirs = [ self.project_path / "app" / "src" / "test", self.project_path / "app" / "src" / "androidTest", ] test_info = {"unit_tests": 0, "instrumentation_tests": 0} for test_dir in test_dirs: if test_dir.exists(): test_files = list(test_dir.rglob("*.kt")) if "test" in str(test_dir): test_info["unit_tests"] = len(test_files) elif "androidTest" in str(test_dir): test_info["instrumentation_tests"] = len(test_files) return test_info def _find_outdated_dependencies(self) -> List[str]: """Find potentially outdated dependencies.""" # Simplified implementation - in reality would check against Maven Central return ["androidx.core:core-ktx:1.8.0 -> 1.12.0", "androidx.compose.ui:ui:1.4.0 -> 1.5.4"] def _scan_security_vulnerabilities(self) -> List[str]: """Scan for security vulnerabilities.""" # Simplified implementation return [] def _check_api_compatibility(self, target_api_level: int) -> Dict[str, Any]: """Check API level compatibility.""" return {"compatible": True, "target_api": target_api_level} def _count_compose_files(self) -> int: """Count Compose-related files.""" count = 0 for source_dir in ["app/src/main/java", "app/src/main/kotlin"]: source_path = self.project_path / source_dir if source_path.exists(): for kt_file in source_path.rglob("*.kt"): try: content = kt_file.read_text(encoding="utf-8") if "@Composable" in content: count += 1 except Exception: continue return count def _generate_recommendations( self, analysis_results: Dict[str, Any], modernization_level: str ) -> List[str]: """Generate modernization recommendations.""" recommendations = [] if modernization_level == "conservative": recommendations.append("Update dependencies gradually") recommendations.append("Add unit tests for critical paths") elif modernization_level == "moderate": recommendations.append("Migrate to Jetpack Compose incrementally") recommendations.append("Implement dependency injection with Hilt") recommendations.append("Adopt Kotlin coroutines for async operations") elif modernization_level == "aggressive": recommendations.append("Complete migration to Jetpack Compose") recommendations.append("Implement clean architecture patterns") recommendations.append("Add comprehensive test coverage") recommendations.append("Optimize build performance with parallel execution") return recommendations

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/normaltusker/kotlin-mcp-server'

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