Spaces:
Sleeping
Sleeping
| """π CodeLint MCP Server - Premium Edition | |
| FastMCP server with 10 tools, mature analyzers, and premium AI integration. | |
| Built for top-tier performance with comprehensive error handling. | |
| """ | |
| import logging | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| # Add src to path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| # Configure logging to stderr only | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| stream=sys.stderr | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # FastMCP imports | |
| from fastmcp import FastMCP | |
| from fastmcp.resources import FunctionResource | |
| # Our premium analyzers | |
| from src.analyzers.python_analyzer import PythonAnalyzer, analyze_python | |
| from src.analyzers.javascript_analyzer import JavaScriptAnalyzer, analyze_javascript | |
| from src.analyzers.project_analyzer import ProjectAnalyzer, analyze_project | |
| from src.analyzers.git_analyzer import GitAnalyzer, analyze_git_diff | |
| # Utilities | |
| from src.utils.language_detector import detect_language | |
| from src.utils.ai_client import generate_ai_response | |
| from src.config import Config | |
| # Create FastMCP server | |
| mcp = FastMCP("codelint-premium") | |
| logger.info("π CodeLint MCP Server - Premium Edition") | |
| logger.info("β All analyzers loaded and ready") | |
| # ============================================================================ | |
| # CORE TOOLS (5 Essential) | |
| # ============================================================================ | |
| async def analyze_code(code: str, language: str = "auto") -> dict[str, Any]: | |
| """ | |
| Comprehensive code analysis with linting, security, and complexity. | |
| Analyzes source code using multiple specialized tools: | |
| - Python: Ruff (linting), Bandit (security), Radon (complexity) | |
| - JavaScript/TypeScript: ESLint (linting), complexity analysis | |
| Args: | |
| code: Source code to analyze | |
| language: Programming language (python, javascript, typescript, or auto) | |
| Returns: | |
| Analysis results with issues, summary, and metadata | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty", "issues": []} | |
| # Auto-detect language if needed | |
| if language == "auto": | |
| language = detect_language(code) | |
| logger.info(f"Auto-detected language: {language}") | |
| # Run appropriate analyzer | |
| if language == "python": | |
| analyzer = PythonAnalyzer() | |
| result = await analyzer.analyze(code) | |
| elif language in ["javascript", "typescript"]: | |
| analyzer = JavaScriptAnalyzer() | |
| result = await analyzer.analyze(code, language=language) | |
| else: | |
| return { | |
| "error": f"Unsupported language: {language}", | |
| "supported": ["python", "javascript", "typescript"], | |
| "issues": [] | |
| } | |
| logger.info(f"Analysis complete: {len(result.get('issues', []))} issues found") | |
| return result | |
| except Exception as e: | |
| logger.error(f"analyze_code failed: {e}", exc_info=True) | |
| return {"error": str(e), "issues": []} | |
| async def check_security(code: str, language: str = "auto") -> dict[str, Any]: | |
| """ | |
| Security vulnerability scanning with severity classification. | |
| Focuses specifically on security issues: | |
| - Python: Bandit security scanner | |
| - JavaScript/TypeScript: Security-focused ESLint rules | |
| Args: | |
| code: Source code to scan for vulnerabilities | |
| language: Programming language (python, javascript, typescript, or auto) | |
| Returns: | |
| Security scan results with vulnerability details | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty", "vulnerabilities": []} | |
| if language == "auto": | |
| language = detect_language(code) | |
| # Python security scanning | |
| if language == "python": | |
| from src.analyzers.python_analyzer import scan_security_python | |
| result = await scan_security_python(code) | |
| return result | |
| # JavaScript security would use ESLint security rules | |
| elif language in ["javascript", "typescript"]: | |
| analyzer = JavaScriptAnalyzer() | |
| result = await analyzer.analyze(code, language=language) | |
| # Filter for security issues only | |
| security_issues = [ | |
| issue for issue in result.get("issues", []) | |
| if "security" in issue.get("message", "").lower() | |
| ] | |
| return { | |
| "vulnerabilities": security_issues, | |
| "summary": { | |
| "total": len(security_issues), | |
| "high": sum(1 for i in security_issues if i.get("severity") == "error"), | |
| "medium": sum(1 for i in security_issues if i.get("severity") == "warning") | |
| } | |
| } | |
| else: | |
| return {"error": f"Security scanning not supported for: {language}", "vulnerabilities": []} | |
| except Exception as e: | |
| logger.error(f"check_security failed: {e}", exc_info=True) | |
| return {"error": str(e), "vulnerabilities": []} | |
| async def complexity_score(code: str, language: str = "auto") -> dict[str, Any]: | |
| """ | |
| Calculate code complexity metrics and maintainability index. | |
| Metrics include: | |
| - Cyclomatic complexity | |
| - Maintainability index | |
| - Function count | |
| - Lines of code | |
| Args: | |
| code: Source code to analyze | |
| language: Programming language (python, javascript, typescript, or auto) | |
| Returns: | |
| Complexity metrics dictionary | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty", "complexity": {}} | |
| if language == "auto": | |
| language = detect_language(code) | |
| if language == "python": | |
| from src.analyzers.python_analyzer import calculate_complexity_python | |
| result = await calculate_complexity_python(code) | |
| return result | |
| elif language in ["javascript", "typescript"]: | |
| from src.analyzers.javascript_analyzer import calculate_complexity_javascript | |
| result = await calculate_complexity_javascript(code) | |
| return result | |
| else: | |
| return {"error": f"Complexity analysis not supported for: {language}", "complexity": {}} | |
| except Exception as e: | |
| logger.error(f"complexity_score failed: {e}", exc_info=True) | |
| return {"error": str(e), "complexity": {}} | |
| async def suggest_fixes(code: str, language: str = "auto", model: str = "grok-4.1") -> dict[str, Any]: | |
| """ | |
| AI-powered fix suggestions for code issues. | |
| Uses premium AI models to: | |
| - Identify problems in code | |
| - Generate fix suggestions with explanations | |
| - Provide complete corrected code | |
| Args: | |
| code: Source code with issues | |
| language: Programming language (auto-detected if not specified) | |
| model: AI model to use (default: grok-4.1 free) | |
| Returns: | |
| Fix suggestions with explanations and corrected code | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty", "suggestions": []} | |
| if language == "auto": | |
| language = detect_language(code) | |
| # First analyze to find issues | |
| analyzer_result = await analyze_code(code=code, language=language) | |
| issues = analyzer_result.get("issues", []) | |
| if not issues: | |
| return { | |
| "message": "No issues found - code looks good!", | |
| "suggestions": [] | |
| } | |
| # Prepare prompt for AI | |
| issues_summary = "\\n".join([ | |
| f"- Line {issue.get('line')}: {issue.get('message')}" | |
| for issue in issues[:10] # Limit to first 10 issues | |
| ]) | |
| prompt = f"""Analyze this {language} code and suggest fixes for the following issues: | |
| ```{language} | |
| {code} | |
| ``` | |
| Issues found: | |
| {issues_summary} | |
| Please provide: | |
| 1. Explanation of each issue | |
| 2. How to fix it | |
| 3. Complete corrected code | |
| Be concise but comprehensive.""" | |
| # Get AI response | |
| ai_response = await generate_ai_response( | |
| prompt=prompt, | |
| model_name=model | |
| ) | |
| return { | |
| "issues_found": len(issues), | |
| "ai_suggestions": ai_response, | |
| "model_used": model | |
| } | |
| except Exception as e: | |
| logger.error(f"suggest_fixes failed: {e}", exc_info=True) | |
| return {"error": str(e), "suggestions": []} | |
| async def get_server_info() -> dict[str, Any]: | |
| """ | |
| Get server capabilities, supported languages, and available AI models. | |
| Returns: | |
| Server information including tools, resources, and models | |
| """ | |
| config = Config() | |
| return { | |
| "server": "CodeLint MCP Premium", | |
| "version": "2.0.0", | |
| "tools": [ | |
| "analyze_code", "check_security", "complexity_score", | |
| "suggest_fixes", "analyze_project", "analyze_git_diff", | |
| "explain_code", "generate_tests", "generate_docs", "get_server_info" | |
| ], | |
| "supported_languages": [ | |
| "python", "javascript", "typescript" | |
| ], | |
| "analyzers": { | |
| "python": ["ruff", "bandit", "radon"], | |
| "javascript": ["eslint", "complexity"], | |
| "typescript": ["eslint", "complexity"] | |
| }, | |
| "ai_models": config.get_dropdown_options(), | |
| "features": [ | |
| "Multi-file project analysis", | |
| "Git diff analysis", | |
| "AI-powered explanations", | |
| "Test generation", | |
| "Documentation generation", | |
| "9 AI model options (3 premium, 6 free)" | |
| ] | |
| } | |
| # ============================================================================ | |
| # COMPETITIVE TOOLS (5 Advanced) | |
| # ============================================================================ | |
| async def analyze_project(project_path: str, max_files: int = 100) -> dict[str, Any]: | |
| """ | |
| Analyze an entire project with multiple files. | |
| Features: | |
| - Parallel file processing | |
| - Multi-language support | |
| - Aggregated results across all files | |
| - Automatic exclusion of common directories (node_modules, __pycache__, etc.) | |
| Args: | |
| project_path: Root directory of the project | |
| max_files: Maximum number of files to analyze (default: 100) | |
| Returns: | |
| Aggregated analysis results for the entire project | |
| """ | |
| try: | |
| result = await analyze_project(project_path=project_path, max_files=max_files) | |
| return result | |
| except Exception as e: | |
| logger.error(f"analyze_project failed: {e}", exc_info=True) | |
| return {"error": str(e), "files_analyzed": 0} | |
| async def analyze_git_diff(repo_path: str, base_ref: str = "HEAD") -> dict[str, Any]: | |
| """ | |
| Analyze only changed files in a Git diff. | |
| Perfect for CI/CD integration and pull request reviews. | |
| Args: | |
| repo_path: Path to Git repository | |
| base_ref: Base reference for comparison (default: HEAD) | |
| Returns: | |
| Analysis results for changed files only | |
| """ | |
| try: | |
| result = await analyze_git_diff(repo_path=repo_path, base_ref=base_ref) | |
| return result | |
| except Exception as e: | |
| logger.error(f"analyze_git_diff failed: {e}", exc_info=True) | |
| return {"error": str(e), "files_changed": 0} | |
| async def explain_code(code: str, language: str = "auto", model: str = "grok-4.1") -> dict[str, Any]: | |
| """ | |
| AI-powered code explanation. | |
| Get clear explanations of what code does, how it works, and potential issues. | |
| Args: | |
| code: Source code to explain | |
| language: Programming language (auto-detected if not specified) | |
| model: AI model to use (default: grok-4.1 free) | |
| Returns: | |
| Detailed code explanation | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty"} | |
| if language == "auto": | |
| language = detect_language(code) | |
| prompt = f"""Explain this {language} code in detail: | |
| ```{language} | |
| {code} | |
| ``` | |
| Please provide: | |
| 1. What the code does (high-level overview) | |
| 2. How it works (step-by-step breakdown) | |
| 3. Any potential issues or improvements | |
| 4. Best practices that are or aren't being followed | |
| Be clear and educational.""" | |
| explanation = await generate_ai_response(prompt=prompt, model_name=model) | |
| return { | |
| "language": language, | |
| "explanation": explanation, | |
| "model_used": model | |
| } | |
| except Exception as e: | |
| logger.error(f"explain_code failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def generate_tests(code: str, language: str = "auto", model: str = "grok-4.1") -> dict[str, Any]: | |
| """ | |
| AI-powered test generation. | |
| Generate comprehensive unit tests for your code. | |
| Args: | |
| code: Source code to generate tests for | |
| language: Programming language (auto-detected if not specified) | |
| model: AI model to use (default: grok-4.1 free) | |
| Returns: | |
| Generated test code with test cases | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty"} | |
| if language == "auto": | |
| language = detect_language(code) | |
| # Determine test framework | |
| test_framework = { | |
| "python": "pytest", | |
| "javascript": "jest", | |
| "typescript": "jest" | |
| }.get(language, "unittest") | |
| prompt = f"""Generate comprehensive unit tests for this {language} code using {test_framework}: | |
| ```{language} | |
| {code} | |
| ``` | |
| Please provide: | |
| 1. Complete test file with all necessary imports | |
| 2. Test cases covering: | |
| - Normal/happy path scenarios | |
| - Edge cases | |
| - Error conditions | |
| - Boundary conditions | |
| 3. Clear test names and docstrings | |
| 4. Setup/teardown if needed | |
| Make tests production-ready and well-documented.""" | |
| tests = await generate_ai_response(prompt=prompt, model_name=model) | |
| return { | |
| "language": language, | |
| "test_framework": test_framework, | |
| "tests": tests, | |
| "model_used": model | |
| } | |
| except Exception as e: | |
| logger.error(f"generate_tests failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def generate_docs(code: str, language: str = "auto", model: str = "grok-4.1") -> dict[str, Any]: | |
| """ | |
| AI-powered documentation generation. | |
| Generate comprehensive documentation including docstrings, comments, and README. | |
| Args: | |
| code: Source code to document | |
| language: Programming language (auto-detected if not specified) | |
| model: AI model to use (default: grok-4.1 free) | |
| Returns: | |
| Generated documentation in appropriate format | |
| """ | |
| try: | |
| if not code or not code.strip(): | |
| return {"error": "Code cannot be empty"} | |
| if language == "auto": | |
| language = detect_language(code) | |
| prompt = f"""Generate comprehensive documentation for this {language} code: | |
| ```{language} | |
| {code} | |
| ``` | |
| Please provide: | |
| 1. Module/file-level docstring | |
| 2. Function/class docstrings following best practices: | |
| - Python: Google/NumPy style | |
| - JavaScript/TypeScript: JSDoc | |
| 3. Inline comments for complex logic | |
| 4. Usage examples | |
| 5. Parameter descriptions and return types | |
| Make documentation clear, complete, and professional.""" | |
| docs = await generate_ai_response(prompt=prompt, model_name=model) | |
| return { | |
| "language": language, | |
| "documentation": docs, | |
| "model_used": model | |
| } | |
| except Exception as e: | |
| logger.error(f"generate_docs failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def prioritize_issues(code: str, language: str = "auto") -> dict[str, Any]: | |
| """ | |
| Smart issue prioritization with severity, impact, and fix effort analysis. | |
| Enriches analysis results with: | |
| - Priority scoring (Critical/High/Medium/Low) | |
| - Impact categories (Security/Reliability/Performance/Style) | |
| - Fix effort estimation (Quick/Medium/Major) | |
| - Time to fix estimates | |
| - Statistics and quick wins identification | |
| Args: | |
| code: Source code to analyze and prioritize | |
| language: Programming language (auto-detected if not specified) | |
| Returns: | |
| Prioritized issues with rich metadata and statistics | |
| """ | |
| try: | |
| # First run analysis | |
| result = await analyze_code(code, language) | |
| issues = result.get("issues", []) | |
| if not issues: | |
| return { | |
| "prioritized_issues": [], | |
| "statistics": {}, | |
| "message": "No issues found!" | |
| } | |
| # Import prioritization system | |
| from src.utils.prioritization import IssuePrioritizer, format_priority_report | |
| # Prioritize and enrich issues | |
| prioritized = IssuePrioritizer.prioritize_issues(issues) | |
| stats = IssuePrioritizer.get_statistics(prioritized) | |
| report = format_priority_report(prioritized) | |
| return { | |
| "prioritized_issues": prioritized, | |
| "statistics": stats, | |
| "report": report, | |
| "language": result.get("language"), | |
| "total_issues": len(prioritized) | |
| } | |
| except Exception as e: | |
| logger.error(f"prioritize_issues failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def auto_fix_code(code: str, language: str = "auto", preview_only: bool = False) -> dict[str, Any]: | |
| """ | |
| Auto-fix common code issues with preview and batch capabilities. | |
| Automatically fixes: | |
| - Missing semicolons | |
| - console.log/debugger statements | |
| - Trailing whitespace | |
| - var to const/let | |
| - == to === | |
| - Unused variables (prefix with _) | |
| Args: | |
| code: Source code to fix | |
| language: Programming language (auto-detected if not specified) | |
| preview_only: If True, only show previews without applying fixes | |
| Returns: | |
| Fixed code with list of applied fixes | |
| """ | |
| try: | |
| # First run analysis | |
| result = await analyze_code(code, language) | |
| issues = result.get("issues", []) | |
| if not issues: | |
| return { | |
| "fixed_code": code, | |
| "applied_fixes": [], | |
| "message": "No issues to fix!" | |
| } | |
| # Import auto-fix engine | |
| from src.utils.auto_fix import AutoFixer, format_fix_report | |
| if preview_only: | |
| # Generate fix summary with previews | |
| fix_summary = AutoFixer.get_fix_summary(code, issues) | |
| report = format_fix_report(fix_summary) | |
| return { | |
| "preview_mode": True, | |
| "fix_summary": fix_summary, | |
| "report": report, | |
| "original_code": code | |
| } | |
| else: | |
| # Apply all fixes | |
| fixed_code, applied_fixes = AutoFixer.batch_fix(code, issues) | |
| return { | |
| "fixed_code": fixed_code, | |
| "applied_fixes": applied_fixes, | |
| "fixes_count": len(applied_fixes), | |
| "original_code": code, | |
| "language": result.get("language") | |
| } | |
| except Exception as e: | |
| logger.error(f"auto_fix_code failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def analyze_dependencies(project_path: str) -> dict[str, Any]: | |
| """ | |
| Analyze project dependencies for vulnerabilities and outdated packages. | |
| Checks for: | |
| - Known CVEs in dependencies | |
| - Outdated packages | |
| - Security vulnerabilities | |
| - License compatibility issues | |
| Supports: | |
| - Node.js (package.json) | |
| - Python (requirements.txt) | |
| Args: | |
| project_path: Path to project directory | |
| Returns: | |
| Dependency analysis with vulnerabilities and recommendations | |
| """ | |
| try: | |
| from src.utils.dependency_analyzer import DependencyAnalyzer, format_dependency_report | |
| analysis = DependencyAnalyzer.analyze_dependencies(project_path) | |
| report = format_dependency_report(analysis) | |
| return { | |
| "analysis": analysis, | |
| "report": report, | |
| "project_path": project_path | |
| } | |
| except Exception as e: | |
| logger.error(f"analyze_dependencies failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| async def detect_duplication(code: str, language: str = "auto", min_lines: int = 5) -> dict[str, Any]: | |
| """ | |
| Detect code duplication and suggest DRY refactoring. | |
| Finds: | |
| - Copy-pasted code blocks | |
| - Similar code patterns | |
| - Refactoring opportunities | |
| Args: | |
| code: Source code to analyze | |
| language: Programming language (auto-detected if not specified) | |
| min_lines: Minimum lines to consider as duplication (default: 5) | |
| Returns: | |
| Duplication analysis with refactoring suggestions | |
| """ | |
| try: | |
| from src.utils.duplication_detector import DuplicationDetector, format_duplication_report | |
| detector = DuplicationDetector(min_lines=min_lines) | |
| analysis = detector.analyze_duplication(code) | |
| report = format_duplication_report(analysis) | |
| return { | |
| "analysis": analysis, | |
| "report": report, | |
| "language": language if language != "auto" else detect_language(code) | |
| } | |
| except Exception as e: | |
| logger.error(f"detect_duplication failed: {e}", exc_info=True) | |
| return {"error": str(e)} | |
| # ============================================================================ | |
| # RESOURCES (Static Information) | |
| # ============================================================================ | |
| async def best_practices_guide() -> str: | |
| """Code quality and best practices guide""" | |
| return """ | |
| # Code Quality Best Practices | |
| ## Python | |
| - Use type hints for better code clarity | |
| - Follow PEP 8 style guide | |
| - Keep functions small and focused | |
| - Use descriptive variable names | |
| - Handle exceptions properly | |
| - Write docstrings for all public functions | |
| - Avoid mutable default arguments | |
| - Use context managers for resources | |
| ## JavaScript/TypeScript | |
| - Use const/let instead of var | |
| - Enable strict mode | |
| - Handle promises properly | |
| - Use async/await for async code | |
| - Validate inputs | |
| - Use === instead of == | |
| - Keep functions pure when possible | |
| - Use TypeScript for large projects | |
| ## Security | |
| - Never use eval() or exec() | |
| - Validate and sanitize all inputs | |
| - Use parameterized queries for databases | |
| - Keep dependencies updated | |
| - Never commit secrets or credentials | |
| - Use HTTPS for all external communications | |
| """ | |
| async def security_guidelines() -> str: | |
| """Security scanning and vulnerability prevention guide""" | |
| return """ | |
| # Security Guidelines | |
| ## Common Vulnerabilities | |
| ### Python | |
| - **Code Injection**: Avoid eval(), exec(), compile() with user input | |
| - **Deserialization**: Never use pickle.loads() on untrusted data | |
| - **Path Traversal**: Validate file paths, don't allow ../ | |
| - **SQL Injection**: Use parameterized queries | |
| - **Command Injection**: Avoid shell=True in subprocess | |
| ### JavaScript/TypeScript | |
| - **XSS**: Sanitize all user inputs before rendering | |
| - **Prototype Pollution**: Avoid Object.assign with user data | |
| - **ReDoS**: Be careful with complex regular expressions | |
| - **Path Traversal**: Validate file paths | |
| - **SQL Injection**: Use parameterized queries | |
| ## Best Practices | |
| - Principle of least privilege | |
| - Defense in depth | |
| - Input validation and sanitization | |
| - Secure defaults | |
| - Regular security updates | |
| - Security testing in CI/CD | |
| """ | |
| async def complexity_guide() -> str: | |
| """Complexity metrics and maintainability guide""" | |
| return """ | |
| # Complexity and Maintainability | |
| ## Cyclomatic Complexity | |
| - **1-10**: Simple, easy to test | |
| - **11-20**: Moderate, needs attention | |
| - **21-50**: Complex, hard to maintain | |
| - **50+**: Very complex, refactor recommended | |
| ## Maintainability Index | |
| - **85-100**: Highly maintainable (Green) | |
| - **65-84**: Moderately maintainable (Yellow) | |
| - **0-64**: Hard to maintain (Red) | |
| ## Tips to Reduce Complexity | |
| - Extract methods/functions | |
| - Use early returns | |
| - Replace nested conditions with guard clauses | |
| - Apply design patterns | |
| - Break large functions into smaller ones | |
| - Use polymorphism instead of conditionals | |
| """ | |
| # ============================================================================ | |
| # GRADIO UI INTEGRATION | |
| # ============================================================================ | |
| def create_gradio_ui(): | |
| """Create premium Gradio UI integrated with MCP""" | |
| import gradio as gr | |
| # Get config instance | |
| cfg = Config() | |
| # Custom CSS for premium look | |
| CUSTOM_CSS = """ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| } | |
| .contain { | |
| background: rgba(17, 24, 39, 0.95) !important; | |
| backdrop-filter: blur(20px) !important; | |
| border-radius: 24px !important; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important; | |
| } | |
| .gr-button { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| border-radius: 12px !important; | |
| transition: all 0.3s !important; | |
| } | |
| .gr-button:hover { | |
| transform: translateY(-2px) !important; | |
| } | |
| """ | |
| # Get model options | |
| model_options = cfg.get_dropdown_options() | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="purple"), css=CUSTOM_CSS) as demo: | |
| gr.Markdown(""" | |
| <div style='text-align: center; padding: 20px 0;'> | |
| <h1>π¨ CodeLint Premium - MCP Edition</h1> | |
| <h3 style='color: #667eea; margin: 10px 0;'>Professional Code Analysis with AI-Powered Insights</h3> | |
| <p style='color: #9ca3af; font-size: 14px;'>Connected to FastMCP Server with 10 tools and 9 AI models</p> | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| # TAB 1: Code Analysis | |
| with gr.Tab("π Code Analysis"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| code_input = gr.Textbox( | |
| label="Source Code", | |
| placeholder="Paste your code here...", | |
| lines=15 | |
| ) | |
| with gr.Column(scale=1): | |
| language = gr.Dropdown( | |
| choices=["auto", "python", "javascript", "typescript"], | |
| value="auto", | |
| label="Language" | |
| ) | |
| model = gr.Dropdown( | |
| choices=model_options, | |
| value="π Grok 4.1 Fast (OpenRouter)", | |
| label="AI Model" | |
| ) | |
| analyze_btn = gr.Button("π Analyze Code", variant="primary") | |
| results_md = gr.Markdown(label="Results") | |
| results_json = gr.JSON(label="Raw Results") | |
| async def analyze_ui(code: str, lang: str, model_name: str): | |
| if not code.strip(): | |
| return "β Please enter code", None | |
| try: | |
| # Call analyzer directly, not the FastMCP tool | |
| original_lang = lang | |
| if lang == "auto": | |
| lang = detect_language(code) | |
| # Log for debugging | |
| import sys | |
| print(f"DEBUG: Original lang: {original_lang}, Detected lang: {lang}", file=sys.stderr) | |
| print(f"DEBUG: Code preview: {code[:100]}...", file=sys.stderr) | |
| if lang == "python": | |
| analyzer = PythonAnalyzer() | |
| result = await analyzer.analyze(code) | |
| elif lang in ["javascript", "typescript"]: | |
| analyzer = JavaScriptAnalyzer() | |
| result = await analyzer.analyze(code, language=lang) | |
| else: | |
| return f"β Unsupported language: {lang}", None | |
| # Extract data from result | |
| issues = result.get("issues", []) | |
| summary = result.get("summary", {}) | |
| # Log for debugging | |
| import sys | |
| print(f"DEBUG: Found {len(issues)} issues", file=sys.stderr) | |
| print(f"DEBUG: Summary: {summary}", file=sys.stderr) | |
| if issues: | |
| print(f"DEBUG: First issue: {issues[0]}", file=sys.stderr) | |
| output = f""" | |
| # π Analysis Results | |
| ## π― Summary | |
| - **Total Issues**: {len(issues)} | |
| - **Errors**: {summary.get('errors', 0)} π΄ | |
| - **Warnings**: {summary.get('warnings', 0)} π‘ | |
| - **Security**: {summary.get('security_issues', 0)} π‘οΈ | |
| ## π Issues Found | |
| """ | |
| if not issues: | |
| output += "\nβ **No issues found! Code looks clean.**\n" | |
| else: | |
| for i, issue in enumerate(issues[:20], 1): | |
| emoji = "π΄" if issue.get("severity") == "error" else "π‘" | |
| line = issue.get('line', 'N/A') | |
| col = issue.get('column', '') | |
| location = f"Line {line}" + (f":{col}" if col else "") | |
| message = issue.get('message', 'No message') | |
| rule_id = issue.get('rule_id', '') | |
| output += f"\n{emoji} **Issue {i}** ({location})\n" | |
| output += f" {message}\n" | |
| if rule_id: | |
| output += f" *Rule: {rule_id}*\n" | |
| if len(issues) > 20: | |
| output += f"\n\n*... and {len(issues) - 20} more issues*" | |
| return output, result | |
| except Exception as e: | |
| import traceback | |
| print(f"ERROR: {traceback.format_exc()}", file=sys.stderr) | |
| return f"β Error: {str(e)}", None | |
| analyze_btn.click( | |
| fn=analyze_ui, | |
| inputs=[code_input, language, model], | |
| outputs=[results_md, results_json] | |
| ) | |
| # Helper function for running analyzer | |
| async def _run_analyzer(code: str, lang: str): | |
| """Helper to run code analysis""" | |
| if lang == "python": | |
| analyzer = PythonAnalyzer() | |
| return await analyzer.analyze(code) | |
| elif lang in ["javascript", "typescript"]: | |
| analyzer = JavaScriptAnalyzer() | |
| return await analyzer.analyze(code, language=lang) | |
| else: | |
| return {"issues": [], "summary": {}} | |
| # Helper function for model mapping (used by all AI features) | |
| def _get_model_map(): | |
| """Map UI model names to actual model IDs""" | |
| return { | |
| "β OpenAI GPT-5 Preview": "gpt-5", | |
| "β Google Gemini 3 Pro": "gemini-3", | |
| "β Claude Sonnet 4.5": "claude-sonnet", | |
| "π Grok 4.1 Fast (OpenRouter)": "grok-4.1", | |
| "π KAT-Coder-Pro V1": "kat-coder", | |
| "π Qwen3-Coder-32B": "qwen-coder", | |
| "π LongCat 7B (OpenRouter)": "longcat", | |
| "π GPT-OSS 4o (OpenRouter)": "gpt-oss", | |
| "π Kimi K2 128k": "kimi" | |
| } | |
| # TAB 2: Project Analysis | |
| with gr.Tab("π¦ Project Analysis"): | |
| project_path = gr.Textbox(label="Project Path", placeholder="C:\\path\\to\\project") | |
| max_files = gr.Slider(10, 500, 100, step=10, label="Max Files") | |
| project_btn = gr.Button("π Analyze Project", variant="primary") | |
| project_results = gr.Markdown() | |
| project_json = gr.JSON() | |
| async def project_ui(path: str, max_f: int): | |
| if not path.strip(): | |
| return "β Enter project path", None | |
| try: | |
| # Call analyzer function directly | |
| from src.analyzers.project_analyzer import analyze_project as analyze_proj | |
| result = await analyze_proj(project_path=path, max_files=max_f) | |
| summary = result.get("summary", {}) | |
| metadata = result.get("metadata", {}) | |
| output = f""" | |
| # π¦ Project Analysis | |
| ## π Summary | |
| - **Files**: {summary.get('files_analyzed', 0)} | |
| - **Errors**: {summary.get('total_errors', 0)} π΄ | |
| - **Warnings**: {summary.get('total_warnings', 0)} π‘ | |
| - **Security**: {summary.get('total_security_issues', 0)} π‘οΈ | |
| - **Lines**: {metadata.get('total_lines_of_code', 0):,} | |
| ## π Languages | |
| {', '.join(metadata.get('languages', []))} | |
| """ | |
| return output, result | |
| except Exception as e: | |
| return f"β Error: {str(e)}", None | |
| project_btn.click( | |
| fn=project_ui, | |
| inputs=[project_path, max_files], | |
| outputs=[project_results, project_json] | |
| ) | |
| # TAB 3: Git Diff | |
| with gr.Tab("π Git Diff"): | |
| repo_path = gr.Textbox(label="Repo Path", placeholder="C:\\path\\to\\repo") | |
| base_ref = gr.Textbox(label="Base Ref", value="HEAD") | |
| git_btn = gr.Button("π Analyze Changes", variant="primary") | |
| git_results = gr.Markdown() | |
| git_json = gr.JSON() | |
| async def git_ui(repo: str, base: str): | |
| if not repo.strip(): | |
| return "β Enter repo path", None | |
| try: | |
| # Call analyzer function directly | |
| from src.analyzers.git_analyzer import analyze_git_diff as analyze_git | |
| result = await analyze_git(repo_path=repo, base_ref=base) | |
| summary = result.get("summary", {}) | |
| output = f""" | |
| # π Git Diff Analysis | |
| ## π Summary | |
| - **Files Changed**: {summary.get('files_changed', 0)} | |
| - **Errors**: {summary.get('total_errors', 0)} π΄ | |
| - **Warnings**: {summary.get('total_warnings', 0)} π‘ | |
| - **Security**: {summary.get('total_security_issues', 0)} π‘οΈ | |
| """ | |
| return output, result | |
| except Exception as e: | |
| return f"β Error: {str(e)}", None | |
| git_btn.click( | |
| fn=git_ui, | |
| inputs=[repo_path, base_ref], | |
| outputs=[git_results, git_json] | |
| ) | |
| # TAB 4: AI Assistant | |
| with gr.Tab("π€ AI Assistant"): | |
| ai_code = gr.Textbox(label="Code", lines=10) | |
| ai_lang = gr.Dropdown(["auto", "python", "javascript", "typescript"], value="auto", label="Language") | |
| ai_model = gr.Dropdown(model_options, value="π Grok 4.1 Fast (OpenRouter)", label="Model") | |
| with gr.Tabs(): | |
| with gr.Tab("Explain"): | |
| explain_btn = gr.Button("π‘ Explain") | |
| explain_out = gr.Markdown() | |
| async def explain_ui(code: str, lang: str, model_name: str): | |
| if not code.strip(): | |
| return "β Enter code" | |
| try: | |
| # Map display name to actual model ID | |
| actual_model = _get_model_map().get(model_name, "grok-4.1") | |
| # Use AI client directly | |
| if lang == "auto": | |
| lang = detect_language(code) | |
| prompt = f"""Explain this {lang} code in detail: | |
| ```{lang} | |
| {code} | |
| ``` | |
| Provide a clear, educational explanation covering: | |
| 1. What the code does | |
| 2. How it works | |
| 3. Potential issues or improvements | |
| """ | |
| explanation = await generate_ai_response(prompt=prompt, model_name=actual_model) | |
| return f"# π€ Explanation\n\n{explanation}" | |
| except Exception as e: | |
| return f"β Error: {str(e)}" | |
| explain_btn.click(fn=explain_ui, inputs=[ai_code, ai_lang, ai_model], outputs=[explain_out]) | |
| with gr.Tab("Generate Tests"): | |
| tests_btn = gr.Button("π§ͺ Generate Tests") | |
| tests_out = gr.Markdown() | |
| async def tests_ui(code: str, lang: str, model_name: str): | |
| if not code.strip(): | |
| return "β Enter code" | |
| try: | |
| # Map display name to actual model ID | |
| actual_model = _get_model_map().get(model_name, "grok-4.1") | |
| # Use AI client directly | |
| if lang == "auto": | |
| lang = detect_language(code) | |
| test_framework = {"python": "pytest", "javascript": "jest", "typescript": "jest"}.get(lang, "unittest") | |
| prompt = f"""Generate comprehensive tests for this {lang} code using {test_framework}: | |
| ```{lang} | |
| {code} | |
| ``` | |
| Include: | |
| - Happy path tests | |
| - Edge cases | |
| - Error conditions | |
| - Clear test names | |
| """ | |
| tests = await generate_ai_response(prompt=prompt, model_name=actual_model) | |
| return f"# π§ͺ Tests\n\n```\n{tests}\n```" | |
| except Exception as e: | |
| return f"β Error: {str(e)}" | |
| tests_btn.click(fn=tests_ui, inputs=[ai_code, ai_lang, ai_model], outputs=[tests_out]) | |
| # TAB 5: Smart Prioritization | |
| with gr.Tab("π― Smart Prioritization"): | |
| prio_code = gr.Textbox(label="Code to Analyze", lines=15, placeholder="Paste your code here...") | |
| prio_lang = gr.Dropdown(["auto", "python", "javascript", "typescript"], value="auto", label="Language") | |
| prio_btn = gr.Button("π Prioritize Issues", variant="primary") | |
| prio_stats = gr.Markdown() | |
| prio_json = gr.JSON() | |
| async def prioritize_ui(code: str, lang: str): | |
| if not code.strip(): | |
| return "β Enter code", None | |
| try: | |
| if lang == "auto": | |
| lang = detect_language(code) | |
| # Analyze code first | |
| result = await _run_analyzer(code, lang) | |
| issues = result.get("issues", []) | |
| if not issues: | |
| return "β No issues found!", result | |
| # Prioritize issues | |
| from src.utils.prioritization import IssuePrioritizer | |
| prioritizer = IssuePrioritizer() | |
| prioritized_issues = prioritizer.prioritize_issues(issues) | |
| stats = prioritizer.get_statistics(prioritized_issues) | |
| top_issue = prioritized_issues[0] if prioritized_issues else None | |
| output = f""" | |
| # π― Issue Prioritization Report | |
| ## π Statistics | |
| - **Total Issues**: {stats['total']} | |
| - **By Severity**: Critical: {stats['by_severity'].get('critical', 0)}, High: {stats['by_severity'].get('high', 0)}, Medium: {stats['by_severity'].get('medium', 0)}, Low: {stats['by_severity'].get('low', 0)} | |
| - **Estimated Fix Time**: {stats['total_fix_time_minutes']} minutes | |
| - **Quick Wins**: {stats['quick_wins']} issues | |
| ## π₯ Top Priority Issue | |
| """ | |
| if top_issue: | |
| metadata = top_issue.get('metadata', {}) | |
| output += f""" | |
| - **Line {top_issue['line']}**: {top_issue['message']} | |
| - **Priority Score**: {top_issue.get('priority_score', 0)} | |
| - **Severity**: {top_issue.get('priority_severity', 'unknown')} | |
| - **Fix Effort**: {metadata.get('fix_effort', 'unknown')} (~{metadata.get('fix_time_minutes', 0)} min) | |
| """ | |
| output += "\n## π All Issues (Prioritized)\n\n" | |
| for i, issue in enumerate(prioritized_issues[:10], 1): | |
| severity = issue.get('priority_severity', 'unknown') | |
| output += f"{i}. **[{severity.upper()}]** Line {issue['line']}: {issue['message']} (Score: {issue.get('priority_score', 0)})\n" | |
| if len(prioritized_issues) > 10: | |
| output += f"\n... and {len(prioritized_issues) - 10} more issues" | |
| return output, {"issues": prioritized_issues, "statistics": stats} | |
| except Exception as e: | |
| import traceback | |
| return f"β Error: {str(e)}\n\n{traceback.format_exc()}", None | |
| prio_btn.click(fn=prioritize_ui, inputs=[prio_code, prio_lang], outputs=[prio_stats, prio_json]) | |
| # TAB 6: Auto-Fix | |
| with gr.Tab("π§ Auto-Fix"): | |
| fix_code = gr.Textbox(label="Code with Issues", lines=15, placeholder="Paste your code here...") | |
| fix_lang = gr.Dropdown(["auto", "python", "javascript", "typescript"], value="auto", label="Language") | |
| fix_btn = gr.Button("β‘ Auto-Fix Issues", variant="primary") | |
| fix_results = gr.Markdown() | |
| with gr.Row(): | |
| fix_code_before = gr.Code(label="β Before (Original)", lines=10, language="python", interactive=False) | |
| fix_code_after = gr.Code(label="β After (Fixed)", lines=10, language="python", interactive=False) | |
| async def autofix_ui(code: str, lang: str): | |
| if not code.strip(): | |
| return "β Enter code", code, code | |
| try: | |
| if lang == "auto": | |
| lang = detect_language(code) | |
| # Analyze code | |
| result = await _run_analyzer(code, lang) | |
| issues = result.get("issues", []) | |
| if not issues: | |
| return "β No issues found to fix!", code, code | |
| # Apply auto-fixes using AutoFixer class | |
| from src.utils.auto_fix import AutoFixer | |
| fixer = AutoFixer() | |
| fixed_code, applied_fixes = fixer.batch_fix(code, issues) | |
| # Get manual review issues | |
| manual_review = [issue for issue in issues if not any(fix['line'] == issue.get('line') for fix in applied_fixes)] | |
| output = f""" | |
| # π§ Auto-Fix Report | |
| ## π Summary | |
| - **Total Issues**: {len(issues)} | |
| - **Fixed**: {len(applied_fixes)} ({len(applied_fixes)/len(issues)*100:.1f}%) | |
| - **Manual Review Needed**: {len(manual_review)} | |
| ## β Fixes Applied | |
| """ | |
| for fix in applied_fixes[:10]: | |
| output += f"- Line {fix['line']}: {fix['fix_description']}\n" | |
| if len(applied_fixes) > 10: | |
| output += f"\n... and {len(applied_fixes) - 10} more fixes" | |
| if manual_review: | |
| output += "\n\n## β οΈ Manual Review Required\n" | |
| for issue in manual_review[:5]: | |
| output += f"- Line {issue.get('line', 'N/A')}: {issue.get('message', 'Unknown issue')}\n" | |
| return output, code, fixed_code | |
| except Exception as e: | |
| import traceback | |
| return f"β Error: {str(e)}\n\n{traceback.format_exc()}", code, code | |
| fix_btn.click(fn=autofix_ui, inputs=[fix_code, fix_lang], outputs=[fix_results, fix_code_before, fix_code_after]) | |
| # TAB 7: Duplication Detector | |
| with gr.Tab("π Duplication Detection"): | |
| dup_code = gr.Textbox(label="Code to Analyze", lines=15, placeholder="Paste your code here...") | |
| dup_threshold = gr.Slider(50, 100, 85, step=5, label="Similarity Threshold (%)") | |
| dup_btn = gr.Button("π Detect Duplicates", variant="primary") | |
| dup_results = gr.Markdown() | |
| dup_json = gr.JSON() | |
| async def duplication_ui(code: str, threshold: int): | |
| if not code.strip(): | |
| return "β Enter code", None | |
| try: | |
| from src.utils.duplication_detector import DuplicationDetector | |
| detector = DuplicationDetector(similarity_threshold=threshold / 100.0) | |
| result = detector.analyze_duplication(code) | |
| stats = result["statistics"] | |
| dup_count = result["duplicates_found"] | |
| severity = result["severity"] | |
| output = f""" | |
| # π Code Duplication Report | |
| ## π Statistics | |
| - **Total Lines**: {stats['total_lines']} | |
| - **Duplicated Lines**: {stats['duplicated_lines']} | |
| - **Duplication Rate**: {stats['duplication_percentage']} | |
| - **Duplicate Blocks**: {dup_count} | |
| - **Severity**: {severity.upper()} | |
| ## π Duplicate Blocks Found | |
| """ | |
| for i, dup in enumerate(result["duplicates"][:10], 1): | |
| output += f""" | |
| ### Block {i} (Similarity: {dup['similarity']:.1f}%) | |
| - **Location 1**: Lines {dup['block1']['start']}-{dup['block1']['end']} | |
| - **Location 2**: Lines {dup['block2']['start']}-{dup['block2']['end']} | |
| - **Suggestion**: {dup['suggestion']} | |
| """ | |
| if len(result["duplicates"]) > 10: | |
| output += f"\n... and {len(result['duplicates']) - 10} more duplicates" | |
| return output, result | |
| except Exception as e: | |
| import traceback | |
| return f"β Error: {str(e)}\n\n{traceback.format_exc()}", None | |
| dup_btn.click(fn=duplication_ui, inputs=[dup_code, dup_threshold], outputs=[dup_results, dup_json]) | |
| # TAB 8: Server Info | |
| with gr.Tab("βΉοΈ About"): | |
| gr.Markdown(""" | |
| # π¨ CodeLint Premium - MCP Edition | |
| ## β¨ Features | |
| - **10 MCP Tools**: Complete analysis suite | |
| - **9 AI Models**: 3 premium β + 6 free π | |
| - **Multi-Language**: Python, JavaScript, TypeScript | |
| - **MCP Protocol**: Fully integrated FastMCP server | |
| ## π§ Tools | |
| - analyze_code, check_security, complexity_score | |
| - suggest_fixes, analyze_project, analyze_git_diff | |
| - explain_code, generate_tests, generate_docs | |
| - get_server_info | |
| ## π Resources | |
| - Best practices guide | |
| - Security guidelines | |
| - Complexity guide | |
| --- | |
| π **Premium Edition** | π **MCP Integrated** | π **Production Ready** | |
| """) | |
| gr.Markdown(""" | |
| <div style='text-align: center; padding: 15px 0; color: #9ca3af; font-size: 13px;'> | |
| <em>Powered by FastMCP Server with 9 AI models</em> | |
| </div> | |
| """) | |
| return demo | |
| # ============================================================================ | |
| # RUN SERVER | |
| # ============================================================================ | |
| def main(): | |
| """Start the FastMCP server with Gradio UI""" | |
| logger.info("π Starting CodeLint Premium MCP Server...") | |
| logger.info(f"π¦ 10 tools available") | |
| logger.info(f"π 3 resources available") | |
| logger.info("π¨ Launching Gradio UI...") | |
| # Create and mount Gradio UI | |
| demo = create_gradio_ui() | |
| # Launch Gradio | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7861, | |
| share=False, | |
| inbrowser=True | |
| ) | |
| if __name__ == "__main__": | |
| main() | |