1use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use tracing::{debug, info, warn};
14use tree_sitter::{Node, Parser, Query, QueryCursor, Tree};
15
16use crate::common::{
17 BaseServer, McpContent, McpServerBase, McpTool, McpToolRequest, McpToolResponse,
18 ServerCapabilities, ServerConfig,
19};
20use crate::{McpToolsError, Result};
21
22pub struct CodeAnalysisServer {
24 base: BaseServer,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ComplexityAnalysis {
30 pub cyclomatic_complexity: u32,
31 pub cognitive_complexity: u32,
32 pub lines_of_code: u32,
33 pub functions: u32,
34 pub classes: u32,
35 pub nested_depth: u32,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct QualityMetrics {
41 pub maintainability_index: f64,
42 pub documentation_ratio: f64,
43 pub test_coverage: f64,
44 pub code_duplication: f64,
45 pub technical_debt: f64,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SecurityAnalysis {
51 pub vulnerabilities: Vec<SecurityVulnerability>,
52 pub risk_score: u32,
53 pub security_hotspots: Vec<String>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SecurityVulnerability {
59 pub severity: String,
60 pub category: String,
61 pub description: String,
62 pub line: u32,
63 pub recommendation: String,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DependencyAnalysis {
69 pub imports: Vec<String>,
70 pub external_dependencies: Vec<String>,
71 pub internal_dependencies: Vec<String>,
72 pub circular_dependencies: Vec<String>,
73 pub unused_imports: Vec<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct CodeAnalysisResult {
79 pub language: String,
80 pub file_path: String,
81 pub complexity: ComplexityAnalysis,
82 pub quality: QualityMetrics,
83 pub security: SecurityAnalysis,
84 pub dependencies: DependencyAnalysis,
85 pub suggestions: Vec<String>,
86}
87
88impl CodeAnalysisServer {
89 pub async fn new(config: ServerConfig) -> Result<Self> {
90 let base = BaseServer::new(config).await?;
91 Ok(Self { base })
92 }
93
94 fn detect_language(&self, file_path: &str, content: &str) -> String {
96 if let Some(extension) = std::path::Path::new(file_path).extension() {
98 match extension.to_str() {
99 Some("rs") => return "rust".to_string(),
100 Some("py") => return "python".to_string(),
101 Some("js") => return "javascript".to_string(),
102 Some("ts") => return "typescript".to_string(),
103 Some("go") => return "go".to_string(),
104 Some("java") => return "java".to_string(),
105 Some("c") => return "c".to_string(),
106 Some("cpp") | Some("cc") | Some("cxx") => return "cpp".to_string(),
107 _ => {}
108 }
109 }
110
111 if content.contains("fn main()") || content.contains("use std::") {
113 "rust".to_string()
114 } else if content.contains("def ") || content.contains("import ") {
115 "python".to_string()
116 } else if content.contains("function ") || content.contains("const ") {
117 "javascript".to_string()
118 } else if content.contains("package main") || content.contains("func ") {
119 "go".to_string()
120 } else if content.contains("public class") || content.contains("import java") {
121 "java".to_string()
122 } else {
123 "unknown".to_string()
124 }
125 }
126
127 async fn analyze_complexity(
129 &self,
130 file_path: &str,
131 content: &str,
132 ) -> Result<ComplexityAnalysis> {
133 let language = self.detect_language(file_path, content);
134
135 let mut parser = Parser::new();
137 let tree_sitter_language = match language.as_str() {
138 "rust" => tree_sitter_rust::language(),
139 "python" => tree_sitter_python::language(),
140 "javascript" => tree_sitter_javascript::language(),
141 "typescript" => tree_sitter_typescript::language_typescript(),
142 "go" => tree_sitter_go::language(),
143 "java" => tree_sitter_java::language(),
144 "c" => tree_sitter_c::language(),
145 "cpp" => tree_sitter_cpp::language(),
146 _ => {
147 return Ok(ComplexityAnalysis {
149 cyclomatic_complexity: 1,
150 cognitive_complexity: 1,
151 lines_of_code: content.lines().count() as u32,
152 functions: 0,
153 classes: 0,
154 nested_depth: 0,
155 });
156 }
157 };
158
159 parser
160 .set_language(tree_sitter_language)
161 .map_err(|e| McpToolsError::Server(format!("Failed to set language: {}", e)))?;
162
163 let tree = parser
165 .parse(content, None)
166 .ok_or_else(|| McpToolsError::Server("Failed to parse code".to_string()))?;
167
168 let root_node = tree.root_node();
170 let mut complexity = ComplexityAnalysis {
171 cyclomatic_complexity: 1, cognitive_complexity: 0,
173 lines_of_code: content.lines().count() as u32,
174 functions: 0,
175 classes: 0,
176 nested_depth: 0,
177 };
178
179 self.traverse_node(&root_node, &mut complexity, 0);
180
181 Ok(complexity)
182 }
183
184 fn traverse_node(&self, node: &Node, complexity: &mut ComplexityAnalysis, depth: u32) {
186 complexity.nested_depth = complexity.nested_depth.max(depth);
187
188 match node.kind() {
189 "function_item"
191 | "function_declaration"
192 | "function_definition"
193 | "method_declaration" => {
194 complexity.functions += 1;
195 complexity.cyclomatic_complexity += 1;
196 }
197 "struct_item" | "impl_item" | "class_declaration" | "class_specifier" => {
199 complexity.classes += 1;
200 }
201 "if_expression" | "if_statement" | "match_expression" | "while_statement"
203 | "for_statement" | "loop_expression" | "try_statement" => {
204 complexity.cyclomatic_complexity += 1;
205 complexity.cognitive_complexity += 1;
206 }
207 "binary_expression" => {
209 if let Some(operator) = node.child_by_field_name("operator") {
210 if matches!(operator.kind(), "&&" | "||" | "and" | "or") {
211 complexity.cyclomatic_complexity += 1;
212 }
213 }
214 }
215 _ => {}
216 }
217
218 for i in 0..node.child_count() {
220 if let Some(child) = node.child(i) {
221 self.traverse_node(&child, complexity, depth + 1);
222 }
223 }
224 }
225
226 async fn analyze_quality(&self, content: &str) -> QualityMetrics {
228 let lines = content.lines().collect::<Vec<_>>();
229 let total_lines = lines.len() as f64;
230
231 let comment_lines = lines
233 .iter()
234 .filter(|line| {
235 let trimmed = line.trim();
236 trimmed.starts_with("//")
237 || trimmed.starts_with("#")
238 || trimmed.starts_with("/*")
239 || trimmed.starts_with("*")
240 || trimmed.starts_with("\"\"\"")
241 || trimmed.starts_with("'''")
242 })
243 .count() as f64;
244
245 let documentation_ratio = if total_lines > 0.0 {
246 (comment_lines / total_lines) * 100.0
247 } else {
248 0.0
249 };
250
251 let maintainability_index = 100.0 - (documentation_ratio * 0.1).max(0.0).min(100.0);
253
254 QualityMetrics {
255 maintainability_index,
256 documentation_ratio,
257 test_coverage: 0.0, code_duplication: 0.0, technical_debt: 0.0, }
261 }
262
263 async fn analyze_security(&self, content: &str, language: &str) -> SecurityAnalysis {
265 let mut vulnerabilities = Vec::new();
266 let lines: Vec<&str> = content.lines().collect();
267
268 for (line_num, line) in lines.iter().enumerate() {
270 let line_lower = line.to_lowercase();
271
272 if line_lower.contains("select")
274 && line_lower.contains("where")
275 && line_lower.contains("+")
276 {
277 vulnerabilities.push(SecurityVulnerability {
278 severity: "High".to_string(),
279 category: "SQL Injection".to_string(),
280 description: "Potential SQL injection vulnerability".to_string(),
281 line: (line_num + 1) as u32,
282 recommendation: "Use parameterized queries".to_string(),
283 });
284 }
285
286 if line_lower.contains("password")
288 && (line_lower.contains("=") || line_lower.contains(":"))
289 {
290 vulnerabilities.push(SecurityVulnerability {
291 severity: "Medium".to_string(),
292 category: "Hardcoded Credentials".to_string(),
293 description: "Potential hardcoded password".to_string(),
294 line: (line_num + 1) as u32,
295 recommendation: "Use environment variables or secure storage".to_string(),
296 });
297 }
298
299 match language {
301 "rust" => {
302 if line.contains("unsafe") {
303 vulnerabilities.push(SecurityVulnerability {
304 severity: "Medium".to_string(),
305 category: "Unsafe Code".to_string(),
306 description: "Unsafe Rust code block".to_string(),
307 line: (line_num + 1) as u32,
308 recommendation: "Review unsafe code for memory safety".to_string(),
309 });
310 }
311 }
312 "c" | "cpp" => {
313 if line.contains("strcpy") || line.contains("sprintf") {
314 vulnerabilities.push(SecurityVulnerability {
315 severity: "High".to_string(),
316 category: "Buffer Overflow".to_string(),
317 description: "Unsafe string function".to_string(),
318 line: (line_num + 1) as u32,
319 recommendation: "Use safe string functions like strncpy or snprintf"
320 .to_string(),
321 });
322 }
323 }
324 _ => {}
325 }
326 }
327
328 let risk_score = vulnerabilities
329 .iter()
330 .map(|v| match v.severity.as_str() {
331 "High" => 30,
332 "Medium" => 15,
333 "Low" => 5,
334 _ => 0,
335 })
336 .sum::<u32>()
337 .min(100);
338
339 SecurityAnalysis {
340 vulnerabilities,
341 risk_score,
342 security_hotspots: vec![], }
344 }
345
346 async fn analyze_dependencies(&self, content: &str, language: &str) -> DependencyAnalysis {
348 let lines: Vec<&str> = content.lines().collect();
349 let mut imports = Vec::new();
350 let mut external_dependencies = Vec::new();
351 let mut internal_dependencies = Vec::new();
352
353 for line in lines {
354 let trimmed = line.trim();
355
356 match language {
357 "rust" => {
358 if trimmed.starts_with("use ") {
359 let import = trimmed
360 .strip_prefix("use ")
361 .unwrap_or("")
362 .split(';')
363 .next()
364 .unwrap_or("")
365 .trim();
366 imports.push(import.to_string());
367
368 if import.starts_with("std::") || import.starts_with("core::") {
369 } else if import.starts_with("crate::")
371 || import.starts_with("super::")
372 || import.starts_with("self::")
373 {
374 internal_dependencies.push(import.to_string());
375 } else {
376 external_dependencies.push(import.to_string());
377 }
378 }
379 }
380 "python" => {
381 if trimmed.starts_with("import ") || trimmed.starts_with("from ") {
382 imports.push(trimmed.to_string());
383 }
385 }
386 "javascript" | "typescript" => {
387 if trimmed.starts_with("import ") || trimmed.contains("require(") {
388 imports.push(trimmed.to_string());
389 }
391 }
392 _ => {}
393 }
394 }
395
396 DependencyAnalysis {
397 imports,
398 external_dependencies,
399 internal_dependencies,
400 circular_dependencies: Vec::new(), unused_imports: Vec::new(), }
403 }
404}
405
406#[async_trait]
407impl McpServerBase for CodeAnalysisServer {
408 async fn get_capabilities(&self) -> Result<ServerCapabilities> {
409 let mut capabilities = self.base.get_capabilities().await?;
410
411 let analysis_tools = vec![
413 McpTool {
414 name: "analyze_code".to_string(),
415 description: "Perform comprehensive code analysis including complexity, quality, security, and dependencies".to_string(),
416 input_schema: serde_json::json!({
417 "type": "object",
418 "properties": {
419 "file_path": {
420 "type": "string",
421 "description": "Path to the code file to analyze"
422 },
423 "content": {
424 "type": "string",
425 "description": "Code content to analyze (alternative to file_path)"
426 },
427 "language": {
428 "type": "string",
429 "description": "Programming language (optional, auto-detected if not provided)"
430 },
431 "analysis_type": {
432 "type": "string",
433 "description": "Type of analysis: 'complexity', 'quality', 'security', 'dependencies', or 'comprehensive'",
434 "enum": ["complexity", "quality", "security", "dependencies", "comprehensive"]
435 }
436 },
437 "required": ["file_path"],
438 "oneOf": [
439 {"required": ["file_path"]},
440 {"required": ["content"]}
441 ]
442 }),
443 category: "code-analysis".to_string(),
444 requires_permission: false,
445 permissions: vec![],
446 },
447 McpTool {
448 name: "detect_language".to_string(),
449 description: "Detect programming language from file path or content".to_string(),
450 input_schema: serde_json::json!({
451 "type": "object",
452 "properties": {
453 "file_path": {
454 "type": "string",
455 "description": "Path to the file"
456 },
457 "content": {
458 "type": "string",
459 "description": "Code content to analyze"
460 }
461 },
462 "oneOf": [
463 {"required": ["file_path"]},
464 {"required": ["content"]}
465 ]
466 }),
467 category: "code-analysis".to_string(),
468 requires_permission: false,
469 permissions: vec![],
470 },
471 McpTool {
472 name: "complexity_analysis".to_string(),
473 description: "Analyze code complexity metrics including cyclomatic complexity and nesting depth".to_string(),
474 input_schema: serde_json::json!({
475 "type": "object",
476 "properties": {
477 "file_path": {
478 "type": "string",
479 "description": "Path to the code file"
480 },
481 "content": {
482 "type": "string",
483 "description": "Code content to analyze"
484 },
485 "language": {
486 "type": "string",
487 "description": "Programming language (optional)"
488 }
489 },
490 "oneOf": [
491 {"required": ["file_path"]},
492 {"required": ["content"]}
493 ]
494 }),
495 category: "code-analysis".to_string(),
496 requires_permission: false,
497 permissions: vec![],
498 },
499 McpTool {
500 name: "security_analysis".to_string(),
501 description: "Analyze code for security vulnerabilities and risks".to_string(),
502 input_schema: serde_json::json!({
503 "type": "object",
504 "properties": {
505 "file_path": {
506 "type": "string",
507 "description": "Path to the code file"
508 },
509 "content": {
510 "type": "string",
511 "description": "Code content to analyze"
512 },
513 "language": {
514 "type": "string",
515 "description": "Programming language (optional)"
516 }
517 },
518 "oneOf": [
519 {"required": ["file_path"]},
520 {"required": ["content"]}
521 ]
522 }),
523 category: "code-analysis".to_string(),
524 requires_permission: false,
525 permissions: vec![],
526 },
527 ];
528
529 capabilities.tools = analysis_tools;
530 Ok(capabilities)
531 }
532
533 async fn handle_tool_request(&self, request: McpToolRequest) -> Result<McpToolResponse> {
534 info!("Handling Code Analysis tool request: {}", request.tool);
535
536 let file_path = request
538 .arguments
539 .get("file_path")
540 .and_then(|v| v.as_str())
541 .unwrap_or("unknown");
542
543 let content = if let Some(content_value) = request.arguments.get("content") {
544 content_value.as_str().unwrap_or("").to_string()
545 } else if file_path != "unknown" {
546 return Ok(McpToolResponse {
549 id: request.id,
550 content: vec![McpContent::text(
551 "File reading not implemented. Please provide 'content' parameter.".to_string(),
552 )],
553 is_error: true,
554 error: Some("File reading not implemented".to_string()),
555 metadata: HashMap::new(),
556 });
557 } else {
558 return Ok(McpToolResponse {
559 id: request.id,
560 content: vec![McpContent::text(
561 "Either 'file_path' or 'content' parameter is required".to_string(),
562 )],
563 is_error: true,
564 error: Some("Missing required parameter".to_string()),
565 metadata: HashMap::new(),
566 });
567 };
568
569 let language = request
570 .arguments
571 .get("language")
572 .and_then(|v| v.as_str())
573 .map(|s| s.to_string())
574 .unwrap_or_else(|| self.detect_language(file_path, &content));
575
576 match request.tool.as_str() {
577 "analyze_code" => {
578 debug!("Performing comprehensive code analysis for: {}", file_path);
579
580 let analysis_type = request
581 .arguments
582 .get("analysis_type")
583 .and_then(|v| v.as_str())
584 .unwrap_or("comprehensive");
585
586 match analysis_type {
587 "comprehensive" => {
588 let complexity = self.analyze_complexity(file_path, &content).await?;
589 let quality = self.analyze_quality(&content).await;
590 let security = self.analyze_security(&content, &language).await;
591 let dependencies = self.analyze_dependencies(&content, &language).await;
592
593 let mut suggestions = Vec::new();
595 if complexity.cyclomatic_complexity > 10 {
596 suggestions
597 .push("Consider breaking down complex functions".to_string());
598 }
599 if complexity.nested_depth > 4 {
600 suggestions
601 .push("Reduce nesting depth for better readability".to_string());
602 }
603 if quality.documentation_ratio < 10.0 {
604 suggestions.push("Add more documentation and comments".to_string());
605 }
606
607 let result = CodeAnalysisResult {
608 language: language.clone(),
609 file_path: file_path.to_string(),
610 complexity,
611 quality,
612 security,
613 dependencies,
614 suggestions,
615 };
616
617 let content_text = format!(
618 "Code Analysis Complete\n\
619 Language: {}\n\
620 Cyclomatic Complexity: {}\n\
621 Lines of Code: {}\n\
622 Functions: {}\n\
623 Security Issues: {}\n\
624 Risk Score: {}/100",
625 result.language,
626 result.complexity.cyclomatic_complexity,
627 result.complexity.lines_of_code,
628 result.complexity.functions,
629 result.security.vulnerabilities.len(),
630 result.security.risk_score
631 );
632
633 let mut metadata = HashMap::new();
634 metadata
635 .insert("analysis_result".to_string(), serde_json::to_value(result)?);
636
637 Ok(McpToolResponse {
638 id: request.id,
639 content: vec![McpContent::text(content_text)],
640 is_error: false,
641 error: None,
642 metadata,
643 })
644 }
645 "complexity" => {
646 let complexity = self.analyze_complexity(file_path, &content).await?;
647 let content_text = format!(
648 "Complexity Analysis\n\
649 Cyclomatic Complexity: {}\n\
650 Cognitive Complexity: {}\n\
651 Lines of Code: {}\n\
652 Functions: {}\n\
653 Classes: {}\n\
654 Max Nesting Depth: {}",
655 complexity.cyclomatic_complexity,
656 complexity.cognitive_complexity,
657 complexity.lines_of_code,
658 complexity.functions,
659 complexity.classes,
660 complexity.nested_depth
661 );
662
663 let mut metadata = HashMap::new();
664 metadata
665 .insert("complexity".to_string(), serde_json::to_value(complexity)?);
666
667 Ok(McpToolResponse {
668 id: request.id,
669 content: vec![McpContent::text(content_text)],
670 is_error: false,
671 error: None,
672 metadata,
673 })
674 }
675 "security" => {
676 let security = self.analyze_security(&content, &language).await;
677 let content_text = format!(
678 "Security Analysis\n\
679 Vulnerabilities Found: {}\n\
680 Risk Score: {}/100\n\
681 Security Hotspots: {}",
682 security.vulnerabilities.len(),
683 security.risk_score,
684 security.security_hotspots.len()
685 );
686
687 let mut metadata = HashMap::new();
688 metadata.insert("security".to_string(), serde_json::to_value(security)?);
689
690 Ok(McpToolResponse {
691 id: request.id,
692 content: vec![McpContent::text(content_text)],
693 is_error: false,
694 error: None,
695 metadata,
696 })
697 }
698 _ => {
699 warn!("Unknown analysis type: {}", analysis_type);
700 Ok(McpToolResponse {
701 id: request.id,
702 content: vec![McpContent::text(format!(
703 "Unknown analysis type: {}",
704 analysis_type
705 ))],
706 is_error: true,
707 error: Some("Invalid analysis type".to_string()),
708 metadata: HashMap::new(),
709 })
710 }
711 }
712 }
713 "detect_language" => {
714 debug!("Detecting language for: {}", file_path);
715 let detected_language = self.detect_language(file_path, &content);
716
717 let content_text = format!("Detected Language: {}", detected_language);
718 let mut metadata = HashMap::new();
719 metadata.insert(
720 "language".to_string(),
721 serde_json::Value::String(detected_language),
722 );
723
724 Ok(McpToolResponse {
725 id: request.id,
726 content: vec![McpContent::text(content_text)],
727 is_error: false,
728 error: None,
729 metadata,
730 })
731 }
732 "complexity_analysis" => {
733 debug!("Analyzing complexity for: {}", file_path);
734 let complexity = self.analyze_complexity(file_path, &content).await?;
735
736 let content_text = format!(
737 "Complexity Analysis\n\
738 Cyclomatic Complexity: {}\n\
739 Lines of Code: {}\n\
740 Functions: {}",
741 complexity.cyclomatic_complexity,
742 complexity.lines_of_code,
743 complexity.functions
744 );
745
746 let mut metadata = HashMap::new();
747 metadata.insert("complexity".to_string(), serde_json::to_value(complexity)?);
748
749 Ok(McpToolResponse {
750 id: request.id,
751 content: vec![McpContent::text(content_text)],
752 is_error: false,
753 error: None,
754 metadata,
755 })
756 }
757 "security_analysis" => {
758 debug!("Analyzing security for: {}", file_path);
759 let security = self.analyze_security(&content, &language).await;
760
761 let content_text = format!(
762 "Security Analysis\n\
763 Vulnerabilities: {}\n\
764 Risk Score: {}/100",
765 security.vulnerabilities.len(),
766 security.risk_score
767 );
768
769 let mut metadata = HashMap::new();
770 metadata.insert("security".to_string(), serde_json::to_value(security)?);
771
772 Ok(McpToolResponse {
773 id: request.id,
774 content: vec![McpContent::text(content_text)],
775 is_error: false,
776 error: None,
777 metadata,
778 })
779 }
780 _ => {
781 warn!("Unknown Code Analysis tool: {}", request.tool);
782 Err(McpToolsError::Server(format!(
783 "Unknown Code Analysis tool: {}",
784 request.tool
785 )))
786 }
787 }
788 }
789
790 async fn get_stats(&self) -> Result<crate::common::ServerStats> {
791 self.base.get_stats().await
792 }
793
794 async fn initialize(&mut self) -> Result<()> {
795 info!("Initializing Code Analysis MCP Server");
796 Ok(())
797 }
798
799 async fn shutdown(&mut self) -> Result<()> {
800 info!("Shutting down Code Analysis MCP Server");
801 Ok(())
802 }
803}