Skip to main content
Glama

CodeGraph CLI MCP Server

by Jakedismo
graph_tools.rs20.6 kB
// ABOUTME: AutoAgents tool definitions for SurrealDB graph analysis // ABOUTME: Type-safe wrappers using AutoAgents derive macros with stateful executor access use crate::autoagents::tools::tool_executor_adapter::GraphToolExecutorAdapter; use autoagents::core::tool::{ToolCallError, ToolInputT, ToolRuntime, ToolT}; use autoagents_derive::{tool, ToolInput}; use serde::{Deserialize, Serialize}; use std::sync::Arc; /// Parameters for get_transitive_dependencies #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct GetTransitiveDependenciesArgs { #[input(description = "The ID of the code node to analyze (e.g., 'nodes:123')")] node_id: String, #[input( description = "Type of dependency relationship to follow (edge-types: 'Calls, Imports, Uses, Extends, Implements, References', default: 'Calls')" )] #[serde(default = "default_edge_type")] edge_type: String, #[input(description = "Maximum traversal depth (1-10, default: 3)")] #[serde(default = "default_depth")] depth: i32, } fn default_edge_type() -> String { "Calls".to_string() } fn default_depth() -> i32 { 3 } /// Get transitive dependencies of a code node #[tool( name = "get_transitive_dependencies", description = "ANSWERS: 'What does this code NEED to work?' Maps the complete chain of forward dependencies to any depth. \ USE WHEN: Planning changes and need to understand blast radius, identifying external library usage, \ finding configuration/infrastructure this code requires. \ UNIQUE VALUE: Shows the SUPPLY CHAIN - what breaks if a dependency disappears. \ FOLLOW-UP: Use get_reverse_dependencies to understand impact in the other direction. \ NOTE: Requires a node_id from semantic_code_search results.", input = GetTransitiveDependenciesArgs, )] pub struct GetTransitiveDependencies { executor: Arc<GraphToolExecutorAdapter>, } impl GetTransitiveDependencies { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for GetTransitiveDependencies { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: GetTransitiveDependenciesArgs = serde_json::from_value(args)?; // Call the actual executor let result = self .executor .execute_sync( "get_transitive_dependencies", serde_json::json!({ "node_id": typed_args.node_id, "edge_type": typed_args.edge_type, "depth": typed_args.depth }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for get_reverse_dependencies #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct GetReverseDependenciesArgs { #[input(description = "The ID of the code node to analyze")] node_id: String, #[input( description = "Type of dependency relationship (edge-types: 'Calls, Imports, Uses, Extends, Implements, References', default: 'Calls')" )] #[serde(default = "default_edge_type")] edge_type: String, #[input(description = "Maximum traversal depth (default: 3)")] #[serde(default = "default_depth")] depth: i32, } /// Get reverse dependencies (what depends on this node) #[tool( name = "get_reverse_dependencies", description = "ANSWERS: 'What breaks if I change this code?' Reveals the complete IMPACT RADIUS - all callers, importers, and users. \ USE WHEN: Before refactoring to understand risk, assessing how widely a component is used, \ finding entry points that trigger this code. \ UNIQUE VALUE: Essential for safe refactoring - shows who depends ON this code (opposite of transitive deps). \ HIGH PRIORITY: Use IMMEDIATELY after semantic_code_search to understand code importance. \ NOTE: Requires a node_id from semantic_code_search results.", input = GetReverseDependenciesArgs, )] pub struct GetReverseDependencies { executor: Arc<GraphToolExecutorAdapter>, } impl GetReverseDependencies { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for GetReverseDependencies { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: GetReverseDependenciesArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "get_reverse_dependencies", serde_json::json!({ "node_id": typed_args.node_id, "edge_type": typed_args.edge_type, "depth": typed_args.depth }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for trace_call_chain #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct TraceCallChainArgs { #[input(description = "Starting node ID for call chain tracing")] node_id: String, #[input(description = "Maximum depth to trace (default: 5)")] #[serde(default = "default_call_chain_depth")] max_depth: i32, } fn default_call_chain_depth() -> i32 { 5 } /// Trace call chain from a starting point #[tool( name = "trace_call_chain", description = "ANSWERS: 'How does execution actually FLOW at runtime?' Traces the dynamic call path from an entry point through nested function calls. \ USE WHEN: Debugging to understand how data flows, mapping request handling from entry to exit, \ finding the sequence of operations a feature performs. \ UNIQUE VALUE: Shows RUNTIME behavior, not just static structure - different from dependency tools which show import/definition relationships. \ EXAMPLE: Entry point 'handleRequest' -> 'validateInput' -> 'queryDatabase' -> 'formatResponse'. \ NOTE: Requires a node_id from semantic_code_search results.", input = TraceCallChainArgs, )] pub struct TraceCallChain { executor: Arc<GraphToolExecutorAdapter>, } impl TraceCallChain { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for TraceCallChain { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: TraceCallChainArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "trace_call_chain", serde_json::json!({ "node_id": typed_args.node_id, "max_depth": typed_args.max_depth }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for detect_cycles #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct DetectCyclesArgs { #[input( description = "Type of dependency edge to check ((edge-types: 'Calls, Imports, Uses, Extends, Implements, References', default: 'Calls')" )] #[serde(default = "default_edge_type")] edge_type: String, } /// Detect circular dependencies #[tool( name = "detect_cycles", description = "ANSWERS: 'Is there hidden coupling creating architectural fragility?' Detects circular dependency chains that indicate design problems. \ USE WHEN: Assessing architectural health, investigating why changes have unexpected side effects, \ finding tightly coupled module groups that should be refactored. \ UNIQUE VALUE: Finds ARCHITECTURAL SMELLS that other tools miss - cycles often cause: build issues, testing difficulties, and change propagation. \ EXAMPLE OUTPUT: A->B->C->A cycle in 'Imports' means these modules cannot be separated. \ BEST PRACTICE: Run after get_transitive_dependencies at depth>=3 to check for hidden cycles.", input = DetectCyclesArgs, )] pub struct DetectCycles { executor: Arc<GraphToolExecutorAdapter>, } impl DetectCycles { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for DetectCycles { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: DetectCyclesArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "detect_circular_dependencies", serde_json::json!({ "edge_type": typed_args.edge_type }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for calculate_coupling #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct CalculateCouplingArgs { #[input(description = "Node ID to analyze coupling for")] node_id: String, } /// Calculate coupling metrics #[tool( name = "calculate_coupling", description = "ANSWERS: 'Is this component stable or volatile? High-impact or isolated?' Quantifies architectural quality with Ca/Ce/I metrics. \ METRICS EXPLAINED: Ca (Afferent) = incoming dependencies (how many USE this), Ce (Efferent) = outgoing dependencies (how many this USES), \ I (Instability) = Ce/(Ca+Ce), range 0-1: I=0 means stable/hard to change, I=1 means unstable/easy to change. \ USE WHEN: Deciding if a component is safe to modify, identifying architectural hotspots, \ validating that stable components (I near 0) don't depend on unstable ones (I near 1). \ INTERPRETATION: High Ca + Low I = critical foundation component (change carefully). Low Ca + High I = leaf component (safe to modify). \ NOTE: Requires a node_id from semantic_code_search results.", input = CalculateCouplingArgs, )] pub struct CalculateCoupling { executor: Arc<GraphToolExecutorAdapter>, } impl CalculateCoupling { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for CalculateCoupling { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: CalculateCouplingArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "calculate_coupling_metrics", serde_json::json!({ "node_id": typed_args.node_id }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for get_hub_nodes #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct GetHubNodesArgs { #[input(description = "Minimum degree (connections) to consider a node a hub (default: 5)")] #[serde(default = "default_min_degree")] min_degree: i32, } fn default_min_degree() -> i32 { 5 } /// Get highly connected hub nodes #[tool( name = "get_hub_nodes", description = "ANSWERS: 'What are the critical architectural centers everything depends on?' Identifies highly-connected components that are potential god objects or architectural bottlenecks. \ USE WHEN: Starting architecture analysis (use BEFORE semantic_code_search for architectural overview), \ finding components where bugs have high blast radius, identifying refactoring candidates. \ UNIQUE VALUE: Reveals the SKELETON of the codebase - the core components everything else builds upon. \ INTERPRETATION: High-degree hub = either well-designed foundation OR problematic god object (use calculate_coupling to distinguish). \ GREAT STARTING POINT: Returns node_ids you can pass to other tools for deeper analysis. \ min_degree=5 finds moderately connected nodes, min_degree=10+ finds major architectural centers.", input = GetHubNodesArgs, )] pub struct GetHubNodes { executor: Arc<GraphToolExecutorAdapter>, } impl GetHubNodes { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for GetHubNodes { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: GetHubNodesArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "get_hub_nodes", serde_json::json!({ "min_degree": typed_args.min_degree }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for semantic_code_search #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct SemanticCodeSearchArgs { #[input( description = "Search query (e.g., How is authentication handled?, database models, error handling code etc.)" )] query: String, #[input(description = "Maximum number of results (1-50, default: 50)")] #[serde(default = "default_search_limit")] limit: i32, #[input( description = "Similarity threshold 0.0-1.0 (optional, default 0.6; lower to broaden matches)" )] #[serde(default = "default_search_threshold")] threshold: f64, } fn default_search_limit() -> i32 { 10 } fn default_search_threshold() -> f64 { 0.6 } /// Semantic code search using AI embeddings, full-text analysis, and graph enrichment #[tool( name = "semantic_code_search", description = "DISCOVERY TOOL: Find code by natural language description. Returns node locations and IDs for deeper analysis. \ USE FOR: Locating code by concept ('authentication', 'error handling'), finding specific identifiers, starting investigations. \ OUTPUT: Provides node_ids (format: 'nodes:uuid') needed by other graph tools. \ LIMITATIONS: Shows WHERE code is, NOT how it connects. Cannot answer: 'What uses this?', 'What does this depend on?', 'Is this stable?' \ REQUIRED FOLLOW-UPS: After finding nodes, use get_reverse_dependencies (who uses this?), get_transitive_dependencies (what does it need?), \ or calculate_coupling (is it stable?) to understand relationships. Search alone provides incomplete answers. \ WORKFLOW: Search -> extract node_id -> analyze with graph tools -> synthesize complete picture.", input = SemanticCodeSearchArgs, )] pub struct SemanticCodeSearch { executor: Arc<GraphToolExecutorAdapter>, } impl SemanticCodeSearch { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for SemanticCodeSearch { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: SemanticCodeSearchArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "semantic_code_search", serde_json::json!({ "query": typed_args.query, "limit": typed_args.limit, "threshold": typed_args.threshold }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } /// Parameters for find_complexity_hotspots #[derive(Serialize, Deserialize, ToolInput, Debug)] pub struct FindComplexityHotspotsArgs { #[input( description = "Minimum cyclomatic complexity threshold (default: 5.0, lower=more results)" )] #[serde(default = "default_min_complexity")] min_complexity: f64, #[input(description = "Maximum number of results (default: 20, max: 100)")] #[serde(default = "default_complexity_limit")] limit: i32, } fn default_min_complexity() -> f64 { 5.0 } fn default_complexity_limit() -> i32 { 20 } /// Find functions/methods with high cyclomatic complexity and their coupling metrics #[tool( name = "find_complexity_hotspots", description = "ANSWERS: 'Where are the riskiest, most complex functions that need refactoring?' \ Identifies code with high cyclomatic complexity combined with coupling analysis. \ USE WHEN: Starting refactoring efforts, assessing technical debt, code review prioritization, \ finding functions that are both complex AND highly connected (highest bug risk). \ UNIQUE VALUE: Combines IMPLEMENTATION complexity (cyclomatic) with ARCHITECTURAL coupling - \ high complexity alone isn't as risky if the code is isolated. Risk = Complexity × Coupling. \ OUTPUT: Returns functions ranked by risk_score with file:line locations and coupling metrics. \ WORKFLOW: find_complexity_hotspots → select high-risk node → get_reverse_dependencies (who depends on it?) \ → calculate_coupling_metrics (stability assessment) → prioritize refactoring. \ INTERPRETATION: risk_score >100 = critical, 50-100 = high, 20-50 = medium, <20 = low priority.", input = FindComplexityHotspotsArgs, )] pub struct FindComplexityHotspots { executor: Arc<GraphToolExecutorAdapter>, } impl FindComplexityHotspots { pub fn new(executor: Arc<GraphToolExecutorAdapter>) -> Self { Self { executor } } } #[async_trait::async_trait] impl ToolRuntime for FindComplexityHotspots { async fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolCallError> { let typed_args: FindComplexityHotspotsArgs = serde_json::from_value(args)?; let result = self .executor .execute_sync( "find_complexity_hotspots", serde_json::json!({ "min_complexity": typed_args.min_complexity, "limit": typed_args.limit }), ) .map_err(|e| { ToolCallError::RuntimeError(Box::new(std::io::Error::new( std::io::ErrorKind::Other, e, ))) })?; Ok(result) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_args_deserialization() { let json = serde_json::json!({ "node_id": "nodes:123", "edge_type": "Imports", "depth": 5 }); let args: GetTransitiveDependenciesArgs = serde_json::from_value(json).unwrap(); assert_eq!(args.node_id, "nodes:123"); assert_eq!(args.edge_type, "Imports"); assert_eq!(args.depth, 5_i32); } #[test] fn test_args_defaults() { let json = serde_json::json!({ "node_id": "nodes:456" }); let args: GetTransitiveDependenciesArgs = serde_json::from_value(json).unwrap(); assert_eq!(args.edge_type, "Calls"); assert_eq!(args.depth, 3_i32); } }

Latest Blog Posts

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