Skip to main content
Glama

Smart Code Search MCP Server

dependency_analyzer.py7.49 kB
""" Dependency Analyzer for building code relationship graphs Tracks imports, function calls, and class inheritance """ import ast import json from pathlib import Path from typing import Dict, List, Set, Tuple, Optional, Any from collections import defaultdict class DependencyAnalyzer: """Analyzes code dependencies and builds relationship graphs""" def __init__(self, project_root: Path): self.project_root = Path(project_root) self.import_graph = defaultdict(set) # file -> imported modules self.call_graph = defaultdict(set) # function -> called functions self.inheritance_tree = defaultdict(set) # class -> parent classes self.reverse_call_graph = defaultdict(set) # function -> callers self.reverse_inheritance = defaultdict(set) # class -> child classes def analyze_file(self, file_path: Path) -> Dict[str, Any]: """Analyze a single Python file for dependencies""" try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() tree = ast.parse(content) relative_path = file_path.relative_to(self.project_root) if file_path.is_absolute() else file_path # Extract imports imports = self._extract_imports(tree) # Analyze each symbol symbols_info = {} for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): symbol_name = node.name # Get function calls within this symbol calls = self._extract_calls(node) # Get inheritance for classes inherits_from = None if isinstance(node, ast.ClassDef): inherits_from = self._extract_inheritance(node) symbols_info[symbol_name] = { 'imports': imports, 'calls': list(calls), 'inherits_from': inherits_from } return symbols_info except Exception as e: return {} def _extract_imports(self, tree: ast.AST) -> List[str]: """Extract all import statements from AST""" imports = [] for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: imports.append(alias.name) elif isinstance(node, ast.ImportFrom): module = node.module or '' for alias in node.names: if alias.name == '*': imports.append(f"{module}.*") else: imports.append(f"{module}.{alias.name}") return imports def _extract_calls(self, node: ast.AST) -> Set[str]: """Extract function calls from a node""" calls = set() for child in ast.walk(node): if isinstance(child, ast.Call): call_name = self._get_call_name(child) if call_name: calls.add(call_name) return calls def _get_call_name(self, call_node: ast.Call) -> Optional[str]: """Get the name of a function being called""" if isinstance(call_node.func, ast.Name): return call_node.func.id elif isinstance(call_node.func, ast.Attribute): # Handle method calls like obj.method() parts = [] node = call_node.func while isinstance(node, ast.Attribute): parts.append(node.attr) node = node.value if isinstance(node, ast.Name): parts.append(node.id) return '.'.join(reversed(parts)) if parts else None return None def _extract_inheritance(self, class_node: ast.ClassDef) -> Optional[str]: """Extract parent classes for a class definition""" parents = [] for base in class_node.bases: if isinstance(base, ast.Name): parents.append(base.id) elif isinstance(base, ast.Attribute): # Handle module.Class inheritance parent_name = [] node = base while isinstance(node, ast.Attribute): parent_name.append(node.attr) node = node.value if isinstance(node, ast.Name): parent_name.append(node.id) parents.append('.'.join(reversed(parent_name))) return ', '.join(parents) if parents else None def build_project_graph(self) -> Dict[str, Any]: """Build complete dependency graph for the project""" project_graph = { 'imports': {}, 'call_graph': {}, 'inheritance': {}, 'statistics': {} } # Find all Python files python_files = list(self.project_root.rglob('*.py')) for file_path in python_files: # Skip common directories to ignore if any(part in file_path.parts for part in ['.git', '__pycache__', 'venv', '.venv']): continue file_info = self.analyze_file(file_path) relative_path = str(file_path.relative_to(self.project_root)) for symbol_name, info in file_info.items(): key = f"{relative_path}::{symbol_name}" # Track imports if info['imports']: project_graph['imports'][key] = info['imports'] # Track calls if info['calls']: project_graph['call_graph'][key] = info['calls'] # Update reverse call graph for called_func in info['calls']: if called_func not in project_graph['call_graph']: project_graph['call_graph'][called_func] = [] # Track inheritance if info.get('inherits_from'): project_graph['inheritance'][key] = info['inherits_from'] # Calculate statistics project_graph['statistics'] = { 'total_files': len(python_files), 'total_imports': len(project_graph['imports']), 'total_calls': sum(len(calls) for calls in project_graph['call_graph'].values()), 'total_inheritance': len(project_graph['inheritance']) } return project_graph def find_dependencies(self, symbol_name: str) -> Dict[str, List[str]]: """Find all dependencies for a given symbol""" dependencies = { 'imports': [], 'calls': [], 'called_by': [], 'inherits_from': None, 'inherited_by': [] } # This would be populated from the built graph # For now, returning structure return dependencies def to_json(self, obj: Any) -> str: """Convert dependency information to JSON""" if isinstance(obj, set): obj = list(obj) return json.dumps(obj, default=str)

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/stevenjjobson/scs-mcp'

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