Skip to main content
Glama

CodeGraph CLI MCP Server

by Jakedismo
semantic.rs31.7 kB
use std::collections::{HashMap, HashSet}; use std::sync::Arc; use anyhow::Result; use tracing::{debug, info, warn}; use tree_sitter::{Node, Tree, TreeCursor}; use codegraph_core::{CodeGraphError, CodeNode, Language}; #[derive(Debug, Clone)] pub struct SemanticContext { pub symbols: HashMap<String, Symbol>, pub scopes: Vec<Scope>, pub imports: Vec<Import>, pub exports: Vec<Export>, pub dependencies: Vec<Dependency>, } #[derive(Debug, Clone)] pub struct Symbol { pub name: String, pub symbol_type: SymbolType, pub scope_id: usize, pub definition_node: Option<String>, // Node ID pub references: Vec<Reference>, pub visibility: Visibility, pub metadata: HashMap<String, String>, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum SymbolType { Function, Variable, Constant, Class, Interface, Struct, Enum, Module, Namespace, Type, Generic, Parameter, Field, Method, Property, Unknown(String), } #[derive(Debug, Clone)] pub struct Scope { pub id: usize, pub parent_id: Option<usize>, pub scope_type: ScopeType, pub start_line: usize, pub end_line: usize, pub symbols: HashSet<String>, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ScopeType { Global, Function, Class, Block, Module, Loop, Conditional, } #[derive(Debug, Clone)] pub struct Reference { pub line: usize, pub column: usize, pub reference_type: ReferenceType, pub context: String, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ReferenceType { Read, Write, Call, Declaration, Definition, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Visibility { Public, Private, Protected, Internal, Package, } #[derive(Debug, Clone)] pub struct Import { pub module: String, pub imported_symbols: Vec<String>, pub alias: Option<String>, pub is_wildcard: bool, pub line: usize, } #[derive(Debug, Clone)] pub struct Export { pub symbol: String, pub export_type: ExportType, pub alias: Option<String>, pub line: usize, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExportType { Named, Default, Namespace, } #[derive(Debug, Clone)] pub struct Dependency { pub symbol: String, pub dependency_type: DependencyType, pub source_scope: usize, pub target_symbol: String, pub strength: DependencyStrength, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum DependencyType { FunctionCall, VariableReference, TypeUsage, Inheritance, Implementation, Import, Composition, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum DependencyStrength { Weak = 1, Medium = 2, Strong = 3, } pub struct SemanticAnalyzer { language_analyzers: HashMap<Language, Box<dyn LanguageSemanticAnalyzer>>, } impl SemanticAnalyzer { pub fn new() -> Self { let mut analyzers: HashMap<Language, Box<dyn LanguageSemanticAnalyzer>> = HashMap::new(); // Register language-specific analyzers analyzers.insert(Language::Rust, Box::new(RustSemanticAnalyzer::new())); analyzers.insert( Language::TypeScript, Box::new(TypeScriptSemanticAnalyzer::new()), ); analyzers.insert( Language::JavaScript, Box::new(JavaScriptSemanticAnalyzer::new()), ); analyzers.insert(Language::Python, Box::new(PythonSemanticAnalyzer::new())); analyzers.insert(Language::Go, Box::new(GoSemanticAnalyzer::new())); Self { language_analyzers } } pub fn analyze( &self, tree: &Tree, content: &str, language: Language, ) -> Result<SemanticContext> { if let Some(analyzer) = self.language_analyzers.get(&language) { analyzer.analyze(tree, content) } else { warn!( "No semantic analyzer available for language: {:?}", language ); Ok(SemanticContext { symbols: HashMap::new(), scopes: vec![Scope { id: 0, parent_id: None, scope_type: ScopeType::Global, start_line: 0, end_line: content.lines().count(), symbols: HashSet::new(), }], imports: Vec::new(), exports: Vec::new(), dependencies: Vec::new(), }) } } pub fn find_affected_symbols( &self, old_context: &SemanticContext, new_context: &SemanticContext, changed_lines: &[usize], ) -> Result<Vec<String>> { let mut affected_symbols = HashSet::new(); // Find directly affected symbols (those in changed lines) for line in changed_lines { // Find symbols defined or referenced on these lines for (name, symbol) in &new_context.symbols { if self.symbol_intersects_lines(symbol, changed_lines) { affected_symbols.insert(name.clone()); } } } // Find symbols that depend on the directly affected ones for symbol_name in affected_symbols.clone() { self.find_dependent_symbols(&new_context, &symbol_name, &mut affected_symbols); } Ok(affected_symbols.into_iter().collect()) } fn symbol_intersects_lines(&self, symbol: &Symbol, lines: &[usize]) -> bool { // Check if symbol's definition or any reference intersects with changed lines for reference in &symbol.references { if lines.contains(&reference.line) { return true; } } false } fn find_dependent_symbols( &self, context: &SemanticContext, symbol_name: &str, affected: &mut HashSet<String>, ) { for dependency in &context.dependencies { if dependency.target_symbol == symbol_name && !affected.contains(&dependency.symbol) && dependency.strength >= DependencyStrength::Medium { affected.insert(dependency.symbol.clone()); // Recursively find dependencies self.find_dependent_symbols(context, &dependency.symbol, affected); } } } pub fn compute_change_impact( &self, old_context: &SemanticContext, new_context: &SemanticContext, ) -> ChangeImpactAnalysis { let mut impact = ChangeImpactAnalysis::new(); // Compare symbols for (name, old_symbol) in &old_context.symbols { match new_context.symbols.get(name) { Some(new_symbol) => { if self.has_symbol_changed(old_symbol, new_symbol) { impact.modified_symbols.insert(name.clone()); // Determine impact level based on symbol type let impact_level = match old_symbol.symbol_type { SymbolType::Function | SymbolType::Method => ImpactLevel::High, SymbolType::Class | SymbolType::Interface => ImpactLevel::Critical, SymbolType::Variable | SymbolType::Field => ImpactLevel::Medium, SymbolType::Constant => ImpactLevel::Low, _ => ImpactLevel::Medium, }; impact.symbol_impacts.insert(name.clone(), impact_level); } } None => { impact.removed_symbols.insert(name.clone()); impact .symbol_impacts .insert(name.clone(), ImpactLevel::High); } } } // Find new symbols for name in new_context.symbols.keys() { if !old_context.symbols.contains_key(name) { impact.added_symbols.insert(name.clone()); impact.symbol_impacts.insert(name.clone(), ImpactLevel::Low); } } // Analyze dependency changes self.analyze_dependency_changes(&old_context, &new_context, &mut impact); impact } fn has_symbol_changed(&self, old_symbol: &Symbol, new_symbol: &Symbol) -> bool { old_symbol.symbol_type != new_symbol.symbol_type || old_symbol.visibility != new_symbol.visibility || old_symbol.references.len() != new_symbol.references.len() || old_symbol.metadata != new_symbol.metadata } fn analyze_dependency_changes( &self, old_context: &SemanticContext, new_context: &SemanticContext, impact: &mut ChangeImpactAnalysis, ) { // Create maps for efficient lookup let old_deps: HashMap<String, &Dependency> = old_context .dependencies .iter() .map(|d| (format!("{}:{}", d.symbol, d.target_symbol), d)) .collect(); let new_deps: HashMap<String, &Dependency> = new_context .dependencies .iter() .map(|d| (format!("{}:{}", d.symbol, d.target_symbol), d)) .collect(); // Find changed dependencies for (key, old_dep) in &old_deps { match new_deps.get(key) { Some(new_dep) => { if old_dep.dependency_type != new_dep.dependency_type || old_dep.strength != new_dep.strength { impact.modified_dependencies.push((*new_dep).clone()); } } None => { impact.removed_dependencies.push((*old_dep).clone()); } } } // Find new dependencies for (key, new_dep) in &new_deps { if !old_deps.contains_key(key) { impact.added_dependencies.push((*new_dep).clone()); } } } } #[derive(Debug)] pub struct ChangeImpactAnalysis { pub added_symbols: HashSet<String>, pub removed_symbols: HashSet<String>, pub modified_symbols: HashSet<String>, pub symbol_impacts: HashMap<String, ImpactLevel>, pub added_dependencies: Vec<Dependency>, pub removed_dependencies: Vec<Dependency>, pub modified_dependencies: Vec<Dependency>, } impl ChangeImpactAnalysis { fn new() -> Self { Self { added_symbols: HashSet::new(), removed_symbols: HashSet::new(), modified_symbols: HashSet::new(), symbol_impacts: HashMap::new(), added_dependencies: Vec::new(), removed_dependencies: Vec::new(), modified_dependencies: Vec::new(), } } pub fn get_max_impact_level(&self) -> ImpactLevel { self.symbol_impacts .values() .max() .cloned() .unwrap_or(ImpactLevel::Low) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ImpactLevel { Low = 1, Medium = 2, High = 3, Critical = 4, } pub trait LanguageSemanticAnalyzer: Send + Sync { fn analyze(&self, tree: &Tree, content: &str) -> Result<SemanticContext>; fn extract_symbols(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Symbol>; fn extract_scopes(&self, cursor: &mut TreeCursor) -> Vec<Scope>; fn extract_imports(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Import>; fn extract_exports(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Export>; fn analyze_dependencies(&self, context: &SemanticContext) -> Vec<Dependency>; } // Rust-specific semantic analyzer pub struct RustSemanticAnalyzer; impl RustSemanticAnalyzer { pub fn new() -> Self { Self } } impl LanguageSemanticAnalyzer for RustSemanticAnalyzer { fn analyze(&self, tree: &Tree, content: &str) -> Result<SemanticContext> { let mut cursor = tree.walk(); let symbols_map: HashMap<String, Symbol> = self .extract_symbols(&mut cursor, content) .into_iter() .map(|s| (s.name.clone(), s)) .collect(); cursor = tree.walk(); let scopes = self.extract_scopes(&mut cursor); cursor = tree.walk(); let imports = self.extract_imports(&mut cursor, content); cursor = tree.walk(); let exports = self.extract_exports(&mut cursor, content); let context = SemanticContext { symbols: symbols_map, scopes, imports, exports, dependencies: Vec::new(), // Will be filled by analyze_dependencies }; let dependencies = self.analyze_dependencies(&context); Ok(SemanticContext { dependencies, ..context }) } fn extract_symbols(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Symbol> { let mut symbols = Vec::new(); let lines: Vec<&str> = content.lines().collect(); self.extract_symbols_recursive(cursor, &mut symbols, &lines, 0); symbols } fn extract_scopes(&self, cursor: &mut TreeCursor) -> Vec<Scope> { let mut scopes = Vec::new(); let mut scope_id = 0; // Global scope scopes.push(Scope { id: scope_id, parent_id: None, scope_type: ScopeType::Global, start_line: 0, end_line: cursor.node().end_position().row, symbols: HashSet::new(), }); scope_id += 1; self.extract_scopes_recursive(cursor, &mut scopes, &mut scope_id, Some(0)); scopes } fn extract_imports(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Import> { let mut imports = Vec::new(); self.extract_imports_recursive(cursor, &mut imports, content); imports } fn extract_exports(&self, cursor: &mut TreeCursor, content: &str) -> Vec<Export> { let mut exports = Vec::new(); // Rust doesn't have explicit exports like JS/TS, but we can find pub items self.extract_exports_recursive(cursor, &mut exports, content); exports } fn analyze_dependencies(&self, context: &SemanticContext) -> Vec<Dependency> { let mut dependencies = Vec::new(); // Analyze function calls, type usage, etc. for (symbol_name, symbol) in &context.symbols { for reference in &symbol.references { if reference.reference_type == ReferenceType::Call { // Look for the called function in the symbol table let called_function = self.extract_called_function_name(&reference.context); if let Some(target) = called_function { if context.symbols.contains_key(&target) { dependencies.push(Dependency { symbol: symbol_name.clone(), dependency_type: DependencyType::FunctionCall, source_scope: symbol.scope_id, target_symbol: target, strength: DependencyStrength::Medium, }); } } } } } dependencies } } impl RustSemanticAnalyzer { fn extract_symbols_recursive( &self, cursor: &mut TreeCursor, symbols: &mut Vec<Symbol>, lines: &[&str], scope_id: usize, ) { let node = cursor.node(); match node.kind() { "function_item" => { if let Some(symbol) = self.extract_function_symbol(node, lines, scope_id) { symbols.push(symbol); } } "struct_item" => { if let Some(symbol) = self.extract_struct_symbol(node, lines, scope_id) { symbols.push(symbol); } } "impl_item" => { // Extract methods from impl blocks if cursor.goto_first_child() { loop { self.extract_symbols_recursive(cursor, symbols, lines, scope_id); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } "let_declaration" => { if let Some(symbol) = self.extract_variable_symbol(node, lines, scope_id) { symbols.push(symbol); } } _ => { // Recursively process children if cursor.goto_first_child() { loop { self.extract_symbols_recursive(cursor, symbols, lines, scope_id); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } } } fn extract_function_symbol( &self, node: Node, lines: &[&str], scope_id: usize, ) -> Option<Symbol> { // Find function name let name = self .find_child_by_kind(node, "identifier") .and_then(|n| self.get_node_text(n, lines))?; let visibility = if self.has_pub_modifier(node) { Visibility::Public } else { Visibility::Private }; Some(Symbol { name, symbol_type: SymbolType::Function, scope_id, definition_node: None, references: Vec::new(), visibility, metadata: HashMap::new(), }) } fn extract_struct_symbol(&self, node: Node, lines: &[&str], scope_id: usize) -> Option<Symbol> { let name = self .find_child_by_kind(node, "type_identifier") .and_then(|n| self.get_node_text(n, lines))?; let visibility = if self.has_pub_modifier(node) { Visibility::Public } else { Visibility::Private }; Some(Symbol { name, symbol_type: SymbolType::Struct, scope_id, definition_node: None, references: Vec::new(), visibility, metadata: HashMap::new(), }) } fn extract_variable_symbol( &self, node: Node, lines: &[&str], scope_id: usize, ) -> Option<Symbol> { let name = self .find_child_by_kind(node, "identifier") .and_then(|n| self.get_node_text(n, lines))?; Some(Symbol { name, symbol_type: SymbolType::Variable, scope_id, definition_node: None, references: Vec::new(), visibility: Visibility::Private, // Local variables are private metadata: HashMap::new(), }) } fn extract_scopes_recursive( &self, cursor: &mut TreeCursor, scopes: &mut Vec<Scope>, scope_id: &mut usize, parent_id: Option<usize>, ) { let node = cursor.node(); let scope_type = match node.kind() { "function_item" => Some(ScopeType::Function), "impl_item" => Some(ScopeType::Class), "block" => Some(ScopeType::Block), "mod_item" => Some(ScopeType::Module), _ => None, }; if let Some(stype) = scope_type { let current_scope_id = *scope_id; scopes.push(Scope { id: current_scope_id, parent_id, scope_type: stype, start_line: node.start_position().row, end_line: node.end_position().row, symbols: HashSet::new(), }); *scope_id += 1; // Recursively process children with this scope as parent if cursor.goto_first_child() { loop { self.extract_scopes_recursive(cursor, scopes, scope_id, Some(current_scope_id)); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } else { // Process children with current parent if cursor.goto_first_child() { loop { self.extract_scopes_recursive(cursor, scopes, scope_id, parent_id); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } } fn extract_imports_recursive( &self, cursor: &mut TreeCursor, imports: &mut Vec<Import>, content: &str, ) { let node = cursor.node(); if node.kind() == "use_declaration" { if let Some(import) = self.parse_rust_use_declaration(node, content) { imports.push(import); } } if cursor.goto_first_child() { loop { self.extract_imports_recursive(cursor, imports, content); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } fn extract_exports_recursive( &self, cursor: &mut TreeCursor, exports: &mut Vec<Export>, content: &str, ) { let node = cursor.node(); // In Rust, pub items are exports if self.has_pub_modifier(node) { match node.kind() { "function_item" | "struct_item" | "enum_item" | "const_item" => { if let Some(name) = self.get_item_name(node, content) { exports.push(Export { symbol: name, export_type: ExportType::Named, alias: None, line: node.start_position().row, }); } } _ => {} } } if cursor.goto_first_child() { loop { self.extract_exports_recursive(cursor, exports, content); if !cursor.goto_next_sibling() { break; } } cursor.goto_parent(); } } fn find_child_by_kind<'a>(&self, node: Node<'a>, kind: &str) -> Option<Node<'a>> { let mut cursor = node.walk(); if cursor.goto_first_child() { loop { if cursor.node().kind() == kind { return Some(cursor.node()); } if !cursor.goto_next_sibling() { break; } } } None } fn get_node_text(&self, node: Node, lines: &[&str]) -> Option<String> { let start = node.start_position(); let end = node.end_position(); if start.row >= lines.len() || end.row >= lines.len() { return None; } if start.row == end.row { let line = lines[start.row]; if end.column <= line.len() { Some(line[start.column..end.column].to_string()) } else { None } } else { let mut text = String::new(); for (i, &line) in lines .iter() .enumerate() .skip(start.row) .take(end.row - start.row + 1) { if i == start.row { text.push_str(&line[start.column..]); } else if i == end.row { text.push_str(&line[..end.column]); } else { text.push_str(line); } if i != end.row { text.push('\n'); } } Some(text) } } fn has_pub_modifier(&self, node: Node) -> bool { let mut cursor = node.walk(); if cursor.goto_first_child() { loop { if cursor.node().kind() == "visibility_modifier" { return true; } if !cursor.goto_next_sibling() { break; } } } false } fn get_item_name(&self, node: Node, content: &str) -> Option<String> { let lines: Vec<&str> = content.lines().collect(); match node.kind() { "function_item" => self .find_child_by_kind(node, "identifier") .and_then(|n| self.get_node_text(n, &lines)), "struct_item" | "enum_item" => self .find_child_by_kind(node, "type_identifier") .and_then(|n| self.get_node_text(n, &lines)), _ => None, } } fn parse_rust_use_declaration(&self, node: Node, content: &str) -> Option<Import> { // Simplified parsing of use declarations let lines: Vec<&str> = content.lines().collect(); if let Some(text) = self.get_node_text(node, &lines) { // Very basic parsing - in a real implementation, this would be more sophisticated let module = text.trim_start_matches("use ").trim_end_matches(';'); Some(Import { module: module.to_string(), imported_symbols: vec![], // Would parse specific symbols alias: None, is_wildcard: module.contains('*'), line: node.start_position().row, }) } else { None } } fn extract_called_function_name(&self, context: &str) -> Option<String> { // Extract function name from call context - very simplified if let Some(open_paren) = context.find('(') { let call_part = &context[..open_paren]; if let Some(func_name) = call_part.split_whitespace().last() { Some(func_name.to_string()) } else { None } } else { None } } } // Placeholder implementations for other languages pub struct TypeScriptSemanticAnalyzer; impl TypeScriptSemanticAnalyzer { pub fn new() -> Self { Self } } impl LanguageSemanticAnalyzer for TypeScriptSemanticAnalyzer { fn analyze(&self, _tree: &Tree, _content: &str) -> Result<SemanticContext> { // TODO: Implement TypeScript-specific analysis Ok(SemanticContext { symbols: HashMap::new(), scopes: Vec::new(), imports: Vec::new(), exports: Vec::new(), dependencies: Vec::new(), }) } fn extract_symbols(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Symbol> { Vec::new() } fn extract_scopes(&self, _cursor: &mut TreeCursor) -> Vec<Scope> { Vec::new() } fn extract_imports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Import> { Vec::new() } fn extract_exports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Export> { Vec::new() } fn analyze_dependencies(&self, _context: &SemanticContext) -> Vec<Dependency> { Vec::new() } } pub struct JavaScriptSemanticAnalyzer; impl JavaScriptSemanticAnalyzer { pub fn new() -> Self { Self } } impl LanguageSemanticAnalyzer for JavaScriptSemanticAnalyzer { fn analyze(&self, _tree: &Tree, _content: &str) -> Result<SemanticContext> { Ok(SemanticContext { symbols: HashMap::new(), scopes: Vec::new(), imports: Vec::new(), exports: Vec::new(), dependencies: Vec::new(), }) } fn extract_symbols(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Symbol> { Vec::new() } fn extract_scopes(&self, _cursor: &mut TreeCursor) -> Vec<Scope> { Vec::new() } fn extract_imports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Import> { Vec::new() } fn extract_exports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Export> { Vec::new() } fn analyze_dependencies(&self, _context: &SemanticContext) -> Vec<Dependency> { Vec::new() } } pub struct PythonSemanticAnalyzer; impl PythonSemanticAnalyzer { pub fn new() -> Self { Self } } impl LanguageSemanticAnalyzer for PythonSemanticAnalyzer { fn analyze(&self, _tree: &Tree, _content: &str) -> Result<SemanticContext> { Ok(SemanticContext { symbols: HashMap::new(), scopes: Vec::new(), imports: Vec::new(), exports: Vec::new(), dependencies: Vec::new(), }) } fn extract_symbols(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Symbol> { Vec::new() } fn extract_scopes(&self, _cursor: &mut TreeCursor) -> Vec<Scope> { Vec::new() } fn extract_imports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Import> { Vec::new() } fn extract_exports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Export> { Vec::new() } fn analyze_dependencies(&self, _context: &SemanticContext) -> Vec<Dependency> { Vec::new() } } pub struct GoSemanticAnalyzer; impl GoSemanticAnalyzer { pub fn new() -> Self { Self } } impl LanguageSemanticAnalyzer for GoSemanticAnalyzer { fn analyze(&self, _tree: &Tree, _content: &str) -> Result<SemanticContext> { Ok(SemanticContext { symbols: HashMap::new(), scopes: Vec::new(), imports: Vec::new(), exports: Vec::new(), dependencies: Vec::new(), }) } fn extract_symbols(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Symbol> { Vec::new() } fn extract_scopes(&self, _cursor: &mut TreeCursor) -> Vec<Scope> { Vec::new() } fn extract_imports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Import> { Vec::new() } fn extract_exports(&self, _cursor: &mut TreeCursor, _content: &str) -> Vec<Export> { Vec::new() } fn analyze_dependencies(&self, _context: &SemanticContext) -> Vec<Dependency> { Vec::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_semantic_analyzer_creation() { let analyzer = SemanticAnalyzer::new(); assert!(analyzer.language_analyzers.contains_key(&Language::Rust)); } #[test] fn test_symbol_type_ordering() { assert_eq!(SymbolType::Function, SymbolType::Function); assert_ne!(SymbolType::Function, SymbolType::Variable); } #[test] fn test_dependency_strength_ordering() { assert!(DependencyStrength::Strong > DependencyStrength::Medium); assert!(DependencyStrength::Medium > DependencyStrength::Weak); } #[test] fn test_impact_level_ordering() { assert!(ImpactLevel::Critical > ImpactLevel::High); assert!(ImpactLevel::High > ImpactLevel::Medium); assert!(ImpactLevel::Medium > ImpactLevel::Low); } }

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/Jakedismo/codegraph-rust'

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