|
|
""" |
|
|
Code Complexity Analyzer MCP Server |
|
|
|
|
|
This MCP server analyzes Python code complexity and provides insights |
|
|
about code quality, maintainability, and potential refactoring opportunities. |
|
|
|
|
|
Tags: building-mcp-track-enterprise |
|
|
""" |
|
|
|
|
|
import ast |
|
|
import re |
|
|
from typing import Dict, List, Tuple |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
def calculate_cyclomatic_complexity(code: str) -> Dict[str, any]: |
|
|
""" |
|
|
Calculate cyclomatic complexity of Python code. |
|
|
|
|
|
Args: |
|
|
code: Python source code as a string |
|
|
|
|
|
Returns: |
|
|
Dictionary with complexity metrics |
|
|
""" |
|
|
try: |
|
|
tree = ast.parse(code) |
|
|
except SyntaxError as e: |
|
|
return {"error": f"Syntax error in code: {str(e)}"} |
|
|
|
|
|
complexity = 1 |
|
|
|
|
|
|
|
|
for node in ast.walk(tree): |
|
|
if isinstance(node, (ast.If, ast.While, ast.For, ast.ExceptHandler)): |
|
|
complexity += 1 |
|
|
elif isinstance(node, ast.BoolOp): |
|
|
complexity += len(node.values) - 1 |
|
|
elif isinstance(node, (ast.ListComp, ast.DictComp, ast.SetComp, ast.GeneratorExp)): |
|
|
complexity += 1 |
|
|
|
|
|
|
|
|
if complexity <= 10: |
|
|
level = "Low (Simple)" |
|
|
recommendation = "Code is easy to understand and maintain." |
|
|
elif complexity <= 20: |
|
|
level = "Moderate" |
|
|
recommendation = "Consider breaking into smaller functions." |
|
|
elif complexity <= 50: |
|
|
level = "High" |
|
|
recommendation = "Refactoring strongly recommended. Break into smaller, focused functions." |
|
|
else: |
|
|
level = "Very High (Critical)" |
|
|
recommendation = "Immediate refactoring required. Code is difficult to test and maintain." |
|
|
|
|
|
return { |
|
|
"cyclomatic_complexity": complexity, |
|
|
"complexity_level": level, |
|
|
"recommendation": recommendation |
|
|
} |
|
|
|
|
|
|
|
|
def analyze_code_metrics(code: str) -> Dict[str, any]: |
|
|
""" |
|
|
Analyze various code metrics including LOC, functions, classes, etc. |
|
|
|
|
|
Args: |
|
|
code: Python source code as a string |
|
|
|
|
|
Returns: |
|
|
Dictionary with code metrics |
|
|
""" |
|
|
try: |
|
|
tree = ast.parse(code) |
|
|
except SyntaxError as e: |
|
|
return {"error": f"Syntax error in code: {str(e)}"} |
|
|
|
|
|
lines = code.split('\n') |
|
|
loc = len(lines) |
|
|
|
|
|
|
|
|
sloc = sum(1 for line in lines if line.strip() and not line.strip().startswith('#')) |
|
|
|
|
|
|
|
|
comments = sum(1 for line in lines if line.strip().startswith('#')) |
|
|
|
|
|
|
|
|
functions = sum(1 for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)) |
|
|
classes = sum(1 for node in ast.walk(tree) if isinstance(node, ast.ClassDef)) |
|
|
|
|
|
|
|
|
comment_ratio = (comments / sloc * 100) if sloc > 0 else 0 |
|
|
|
|
|
return { |
|
|
"lines_of_code": loc, |
|
|
"source_lines_of_code": sloc, |
|
|
"comment_lines": comments, |
|
|
"comment_ratio_percent": round(comment_ratio, 2), |
|
|
"functions": functions, |
|
|
"classes": classes |
|
|
} |
|
|
|
|
|
|
|
|
def detect_code_smells(code: str) -> List[str]: |
|
|
""" |
|
|
Detect common code smells in Python code. |
|
|
|
|
|
Args: |
|
|
code: Python source code as a string |
|
|
|
|
|
Returns: |
|
|
List of detected code smells |
|
|
""" |
|
|
smells = [] |
|
|
|
|
|
try: |
|
|
tree = ast.parse(code) |
|
|
except SyntaxError: |
|
|
return ["Syntax error prevents analysis"] |
|
|
|
|
|
|
|
|
for node in ast.walk(tree): |
|
|
if isinstance(node, ast.FunctionDef): |
|
|
if hasattr(node, 'end_lineno') and hasattr(node, 'lineno'): |
|
|
func_lines = node.end_lineno - node.lineno |
|
|
if func_lines > 50: |
|
|
smells.append(f"Long function '{node.name}' ({func_lines} lines)") |
|
|
|
|
|
|
|
|
if isinstance(node, ast.FunctionDef): |
|
|
param_count = len(node.args.args) |
|
|
if param_count > 5: |
|
|
smells.append(f"Function '{node.name}' has too many parameters ({param_count})") |
|
|
|
|
|
|
|
|
if isinstance(node, (ast.If, ast.For, ast.While)): |
|
|
depth = sum(1 for parent in ast.walk(tree) |
|
|
if isinstance(parent, (ast.If, ast.For, ast.While))) |
|
|
if depth > 4: |
|
|
smells.append("Deeply nested code blocks detected") |
|
|
break |
|
|
|
|
|
|
|
|
lines = [line.strip() for line in code.split('\n') if line.strip()] |
|
|
if len(lines) != len(set(lines)): |
|
|
duplicate_count = len(lines) - len(set(lines)) |
|
|
if duplicate_count > 3: |
|
|
smells.append(f"Possible duplicate code: {duplicate_count} duplicate lines") |
|
|
|
|
|
if not smells: |
|
|
smells.append("No obvious code smells detected!") |
|
|
|
|
|
return smells |
|
|
|
|
|
|
|
|
def full_code_analysis(code: str) -> str: |
|
|
""" |
|
|
Perform complete code analysis combining all metrics. |
|
|
|
|
|
Args: |
|
|
code: Python source code as a string |
|
|
|
|
|
Returns: |
|
|
Formatted analysis report |
|
|
""" |
|
|
if not code.strip(): |
|
|
return "Please provide Python code to analyze." |
|
|
|
|
|
complexity = calculate_cyclomatic_complexity(code) |
|
|
metrics = analyze_code_metrics(code) |
|
|
smells = detect_code_smells(code) |
|
|
|
|
|
|
|
|
report = "# Code Analysis Report\n\n" |
|
|
|
|
|
|
|
|
report += "## Complexity Analysis\n" |
|
|
if "error" in complexity: |
|
|
report += f"Error: {complexity['error']}\n\n" |
|
|
else: |
|
|
report += f"- **Cyclomatic Complexity**: {complexity['cyclomatic_complexity']}\n" |
|
|
report += f"- **Complexity Level**: {complexity['complexity_level']}\n" |
|
|
report += f"- **Recommendation**: {complexity['recommendation']}\n\n" |
|
|
|
|
|
|
|
|
report += "## Code Metrics\n" |
|
|
if "error" in metrics: |
|
|
report += f"Error: {metrics['error']}\n\n" |
|
|
else: |
|
|
report += f"- **Total Lines**: {metrics['lines_of_code']}\n" |
|
|
report += f"- **Source Lines**: {metrics['source_lines_of_code']}\n" |
|
|
report += f"- **Comment Lines**: {metrics['comment_lines']}\n" |
|
|
report += f"- **Comment Ratio**: {metrics['comment_ratio_percent']}%\n" |
|
|
report += f"- **Functions**: {metrics['functions']}\n" |
|
|
report += f"- **Classes**: {metrics['classes']}\n\n" |
|
|
|
|
|
|
|
|
report += "## Code Smells Detected\n" |
|
|
for smell in smells: |
|
|
report += f"- {smell}\n" |
|
|
|
|
|
return report |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Code Complexity Analyzer MCP Server") as demo: |
|
|
gr.Markdown(""" |
|
|
# Code Complexity Analyzer MCP Server |
|
|
|
|
|
Analyze Python code complexity and quality metrics. This MCP server provides: |
|
|
- Cyclomatic complexity calculation |
|
|
- Code metrics (LOC, comments, functions, classes) |
|
|
- Code smell detection |
|
|
- Refactoring recommendations |
|
|
|
|
|
**Category**: Enterprise MCP Server |
|
|
**Tags**: building-mcp-track-enterprise |
|
|
""") |
|
|
|
|
|
with gr.Tab("Full Analysis"): |
|
|
code_input = gr.Code( |
|
|
label="Python Code", |
|
|
language="python", |
|
|
lines=20, |
|
|
value="# Paste your Python code here\ndef example():\n pass" |
|
|
) |
|
|
analyze_btn = gr.Button("Analyze Code", variant="primary") |
|
|
analysis_output = gr.Markdown(label="Analysis Report") |
|
|
|
|
|
analyze_btn.click( |
|
|
fn=full_code_analysis, |
|
|
inputs=code_input, |
|
|
outputs=analysis_output |
|
|
) |
|
|
|
|
|
with gr.Tab("Complexity Only"): |
|
|
complexity_input = gr.Code(label="Python Code", language="python", lines=15) |
|
|
complexity_btn = gr.Button("Calculate Complexity") |
|
|
complexity_output = gr.JSON(label="Complexity Metrics") |
|
|
|
|
|
complexity_btn.click( |
|
|
fn=calculate_cyclomatic_complexity, |
|
|
inputs=complexity_input, |
|
|
outputs=complexity_output |
|
|
) |
|
|
|
|
|
with gr.Tab("Code Metrics"): |
|
|
metrics_input = gr.Code(label="Python Code", language="python", lines=15) |
|
|
metrics_btn = gr.Button("Analyze Metrics") |
|
|
metrics_output = gr.JSON(label="Code Metrics") |
|
|
|
|
|
metrics_btn.click( |
|
|
fn=analyze_code_metrics, |
|
|
inputs=metrics_input, |
|
|
outputs=metrics_output |
|
|
) |
|
|
|
|
|
with gr.Tab("Code Smells"): |
|
|
smells_input = gr.Code(label="Python Code", language="python", lines=15) |
|
|
smells_btn = gr.Button("Detect Code Smells") |
|
|
smells_output = gr.JSON(label="Detected Code Smells") |
|
|
|
|
|
smells_btn.click( |
|
|
fn=detect_code_smells, |
|
|
inputs=smells_input, |
|
|
outputs=smells_output |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(mcp_server=True, server_name="0.0.0.0", server_port=7860) |
|
|
|