codeprism_mcp/
resources.rs

1//! MCP Resources implementation
2//!
3//! Resources allow servers to share data that provides context to language models,
4//! such as files, database schemas, or application-specific information.
5//! Each resource is uniquely identified by a URI.
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11use crate::CodePrismMcpServer;
12
13/// Resource capabilities as defined by MCP
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ResourceCapabilities {
16    /// Whether the client can subscribe to be notified of changes to individual resources
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub subscribe: Option<bool>,
19    /// Whether the server will emit notifications when the list of available resources changes
20    #[serde(rename = "listChanged")]
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub list_changed: Option<bool>,
23}
24
25/// MCP Resource definition
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Resource {
28    /// Unique identifier for the resource (URI)
29    pub uri: String,
30    /// Optional human-readable name for display purposes
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub name: Option<String>,
33    /// Optional human-readable description
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub description: Option<String>,
36    /// MIME type of the resource
37    #[serde(rename = "mimeType")]
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub mime_type: Option<String>,
40}
41
42/// Resource content (for reading resources)
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ResourceContent {
45    /// The resource URI
46    pub uri: String,
47    /// MIME type of the content
48    #[serde(rename = "mimeType")]
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub mime_type: Option<String>,
51    /// Text content (for text resources)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub text: Option<String>,
54    /// Binary content (base64 encoded for binary resources)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub blob: Option<String>,
57}
58
59/// Parameters for listing resources
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ListResourcesParams {
62    /// Optional cursor for pagination
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub cursor: Option<String>,
65}
66
67/// Result of listing resources
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ListResourcesResult {
70    /// List of available resources
71    pub resources: Vec<Resource>,
72    /// Optional cursor for pagination
73    #[serde(rename = "nextCursor")]
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub next_cursor: Option<String>,
76}
77
78/// Parameters for reading a resource
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ReadResourceParams {
81    /// URI of the resource to read
82    pub uri: String,
83}
84
85/// Result of reading a resource
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ReadResourceResult {
88    /// List of resource content (can contain multiple items)
89    pub contents: Vec<ResourceContent>,
90}
91
92/// Resource manager for MCP server
93pub struct ResourceManager {
94    server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>,
95}
96
97impl ResourceManager {
98    /// Create a new resource manager
99    pub fn new(server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>) -> Self {
100        Self { server }
101    }
102
103    /// Extract source context around a line number from a file
104    fn extract_source_context(
105        &self,
106        file_path: &std::path::Path,
107        line_number: usize,
108        context_lines: usize,
109    ) -> Option<serde_json::Value> {
110        // Read the file content
111        let content = match std::fs::read_to_string(file_path) {
112            Ok(content) => content,
113            Err(_) => return None,
114        };
115
116        let lines: Vec<&str> = content.lines().collect();
117        let total_lines = lines.len();
118
119        if line_number == 0 || line_number > total_lines {
120            return None;
121        }
122
123        // Convert to 0-based indexing
124        let target_line_idx = line_number - 1;
125
126        // Calculate context range (with bounds checking)
127        let start_idx = target_line_idx.saturating_sub(context_lines);
128        let end_idx = std::cmp::min(target_line_idx + context_lines, total_lines - 1);
129
130        // Extract context lines with line numbers
131        let mut context_lines_with_numbers = Vec::new();
132        for (i, _) in lines.iter().enumerate().take(end_idx + 1).skip(start_idx) {
133            context_lines_with_numbers.push(serde_json::json!({
134                "line_number": i + 1,
135                "content": lines[i],
136                "is_target": i == target_line_idx
137            }));
138        }
139
140        Some(serde_json::json!({
141            "target_line": line_number,
142            "context_range": {
143                "start_line": start_idx + 1,
144                "end_line": end_idx + 1
145            },
146            "lines": context_lines_with_numbers
147        }))
148    }
149
150    /// Create enhanced node information with source context
151    fn create_node_info_with_context(
152        &self,
153        node: &codeprism_core::Node,
154        context_lines: usize,
155    ) -> serde_json::Value {
156        let mut node_info = serde_json::json!({
157            "id": node.id.to_hex(),
158            "name": node.name,
159            "kind": format!("{:?}", node.kind),
160            "language": format!("{:?}", node.lang),
161            "file": node.file.display().to_string(),
162            "span": {
163                "start_line": node.span.start_line,
164                "end_line": node.span.end_line,
165                "start_column": node.span.start_column,
166                "end_column": node.span.end_column
167            },
168            "signature": node.signature
169        });
170
171        // Add source context around the symbol location
172        if let Some(context) =
173            self.extract_source_context(&node.file, node.span.start_line, context_lines)
174        {
175            node_info["source_context"] = context;
176        }
177
178        node_info
179    }
180
181    /// List available resources
182    pub async fn list_resources(
183        &self,
184        _params: ListResourcesParams,
185    ) -> Result<ListResourcesResult> {
186        let server = self.server.read().await;
187
188        let mut resources = Vec::new();
189
190        // Add repository-level resources
191        if let Some(repo_path) = server.repository_path() {
192            // Repository root resource
193            resources.push(Resource {
194                uri: "codeprism://repository/".to_string(),
195                name: Some("Repository Root".to_string()),
196                description: Some("Root directory of the indexed repository".to_string()),
197                mime_type: Some("application/vnd.codeprism.directory".to_string()),
198            });
199
200            // Repository stats resource
201            resources.push(Resource {
202                uri: "codeprism://repository/stats".to_string(),
203                name: Some("Repository Statistics".to_string()),
204                description: Some("Statistical information about the repository".to_string()),
205                mime_type: Some("application/json".to_string()),
206            });
207
208            // Repository configuration resource
209            resources.push(Resource {
210                uri: "codeprism://repository/config".to_string(),
211                name: Some("Repository Configuration".to_string()),
212                description: Some("Configuration and metadata for the repository".to_string()),
213                mime_type: Some("application/json".to_string()),
214            });
215
216            // File tree resource
217            resources.push(Resource {
218                uri: "codeprism://repository/tree".to_string(),
219                name: Some("File Tree".to_string()),
220                description: Some("Complete file tree structure of the repository".to_string()),
221                mime_type: Some("application/json".to_string()),
222            });
223
224            // Graph resource
225            resources.push(Resource {
226                uri: "codeprism://graph/repository".to_string(),
227                name: Some("Repository Graph".to_string()),
228                description: Some("Graph structure and statistics for the repository".to_string()),
229                mime_type: Some("application/json".to_string()),
230            });
231
232            // Symbol resources by type
233            resources.push(Resource {
234                uri: "codeprism://symbols/functions".to_string(),
235                name: Some("Functions".to_string()),
236                description: Some("All function symbols in the repository".to_string()),
237                mime_type: Some("application/json".to_string()),
238            });
239
240            resources.push(Resource {
241                uri: "codeprism://symbols/classes".to_string(),
242                name: Some("Classes".to_string()),
243                description: Some("All class symbols in the repository".to_string()),
244                mime_type: Some("application/json".to_string()),
245            });
246
247            resources.push(Resource {
248                uri: "codeprism://symbols/variables".to_string(),
249                name: Some("Variables".to_string()),
250                description: Some("All variable symbols in the repository".to_string()),
251                mime_type: Some("application/json".to_string()),
252            });
253
254            resources.push(Resource {
255                uri: "codeprism://symbols/modules".to_string(),
256                name: Some("Modules".to_string()),
257                description: Some("All module symbols in the repository".to_string()),
258                mime_type: Some("application/json".to_string()),
259            });
260
261            // Add quality metrics dashboard resource
262            resources.push(Resource {
263                uri: "codeprism://metrics/quality_dashboard".to_string(),
264                name: Some("Quality Dashboard".to_string()),
265                description: Some(
266                    "Code quality metrics, complexity analysis, and technical debt assessment"
267                        .to_string(),
268                ),
269                mime_type: Some("application/json".to_string()),
270            });
271
272            // Add architectural overview resources
273            resources.push(Resource {
274                uri: "codeprism://architecture/layers".to_string(),
275                name: Some("Architectural Layers".to_string()),
276                description: Some(
277                    "Layer structure identification and architectural organization".to_string(),
278                ),
279                mime_type: Some("application/json".to_string()),
280            });
281
282            resources.push(Resource {
283                uri: "codeprism://architecture/patterns".to_string(),
284                name: Some("Architectural Patterns".to_string()),
285                description: Some(
286                    "Detected design patterns and architectural structures".to_string(),
287                ),
288                mime_type: Some("application/json".to_string()),
289            });
290
291            resources.push(Resource {
292                uri: "codeprism://architecture/dependencies".to_string(),
293                name: Some("Architectural Dependencies".to_string()),
294                description: Some(
295                    "High-level dependency analysis and architectural dependency graph".to_string(),
296                ),
297                mime_type: Some("application/json".to_string()),
298            });
299
300            // Add individual file resources from the repository
301            if let Ok(scan_result) = server.scanner().discover_files(repo_path) {
302                for file_path in scan_result.iter().take(100) {
303                    // Limit to first 100 files
304                    if let Ok(relative_path) = file_path.strip_prefix(repo_path) {
305                        let uri =
306                            format!("codeprism://repository/file/{}", relative_path.display());
307                        let name = file_path
308                            .file_name()
309                            .and_then(|n| n.to_str())
310                            .unwrap_or("unknown")
311                            .to_string();
312
313                        let mime_type = detect_mime_type(file_path);
314
315                        resources.push(Resource {
316                            uri,
317                            name: Some(name),
318                            description: Some(format!("Source file: {}", relative_path.display())),
319                            mime_type: Some(mime_type),
320                        });
321                    }
322                }
323            }
324        }
325
326        Ok(ListResourcesResult {
327            resources,
328            next_cursor: None, // Simple implementation without pagination
329        })
330    }
331
332    /// Read a specific resource
333    pub async fn read_resource(&self, params: ReadResourceParams) -> Result<ReadResourceResult> {
334        let server = self.server.read().await;
335
336        let content = if params.uri.starts_with("codeprism://repository/")
337            || params.uri.starts_with("codeprism://graph/")
338            || params.uri.starts_with("codeprism://symbols/")
339            || params.uri.starts_with("codeprism://metrics/")
340            || params.uri.starts_with("codeprism://architecture/")
341        {
342            self.handle_repository_resource(&server, &params.uri)
343                .await?
344        } else {
345            return Err(anyhow::anyhow!("Unsupported resource URI: {}", params.uri));
346        };
347
348        Ok(ReadResourceResult {
349            contents: vec![content],
350        })
351    }
352
353    /// Handle repository-specific resources
354    async fn handle_repository_resource(
355        &self,
356        server: &CodePrismMcpServer,
357        uri: &str,
358    ) -> Result<ResourceContent> {
359        let repo_path = server
360            .repository_path()
361            .ok_or_else(|| anyhow::anyhow!("No repository initialized"))?;
362
363        match uri {
364            "codeprism://repository/" => {
365                // Repository root information
366                let info = serde_json::json!({
367                    "path": repo_path.display().to_string(),
368                    "name": repo_path.file_name().and_then(|n| n.to_str()).unwrap_or("repository"),
369                    "type": "repository_root"
370                });
371
372                Ok(ResourceContent {
373                    uri: uri.to_string(),
374                    mime_type: Some("application/json".to_string()),
375                    text: Some(serde_json::to_string_pretty(&info)?),
376                    blob: None,
377                })
378            }
379
380            "codeprism://repository/stats" => {
381                // Repository statistics
382                let stats = server.repository_manager().get_total_stats();
383                let stats_json = serde_json::json!({
384                    "total_repositories": stats.get("repositories").unwrap_or(&0),
385                    "total_files": stats.get("files").unwrap_or(&0),
386                    "total_nodes": stats.get("nodes").unwrap_or(&0),
387                    "total_edges": stats.get("edges").unwrap_or(&0)
388                });
389
390                Ok(ResourceContent {
391                    uri: uri.to_string(),
392                    mime_type: Some("application/json".to_string()),
393                    text: Some(serde_json::to_string_pretty(&stats_json)?),
394                    blob: None,
395                })
396            }
397
398            "codeprism://repository/config" => {
399                // Repository configuration
400                let config = serde_json::json!({
401                    "path": repo_path.display().to_string(),
402                    "scanner_config": {
403                        "supported_extensions": ["js", "ts", "py", "java"],
404                        "ignore_patterns": [".git", "node_modules", "__pycache__"]
405                    }
406                });
407
408                Ok(ResourceContent {
409                    uri: uri.to_string(),
410                    mime_type: Some("application/json".to_string()),
411                    text: Some(serde_json::to_string_pretty(&config)?),
412                    blob: None,
413                })
414            }
415
416            "codeprism://repository/tree" => {
417                // File tree structure
418                let files = server.scanner().discover_files(repo_path)?;
419                let tree = files
420                    .iter()
421                    .filter_map(|path| path.strip_prefix(repo_path).ok())
422                    .map(|path| path.display().to_string())
423                    .collect::<Vec<_>>();
424
425                let tree_json = serde_json::json!({
426                    "files": tree,
427                    "total_count": tree.len()
428                });
429
430                Ok(ResourceContent {
431                    uri: uri.to_string(),
432                    mime_type: Some("application/json".to_string()),
433                    text: Some(serde_json::to_string_pretty(&tree_json)?),
434                    blob: None,
435                })
436            }
437
438            "codeprism://graph/repository" => {
439                // Repository graph structure
440                let graph_stats = server.graph_store().get_stats();
441                let graph_json = serde_json::json!({
442                    "nodes": graph_stats.total_nodes,
443                    "edges": graph_stats.total_edges,
444                    "files": graph_stats.total_files,
445                    "nodes_by_kind": graph_stats.nodes_by_kind,
446                    "last_updated": std::time::SystemTime::now()
447                        .duration_since(std::time::UNIX_EPOCH)
448                        .unwrap_or_default()
449                        .as_secs()
450                });
451
452                Ok(ResourceContent {
453                    uri: uri.to_string(),
454                    mime_type: Some("application/json".to_string()),
455                    text: Some(serde_json::to_string_pretty(&graph_json)?),
456                    blob: None,
457                })
458            }
459
460            "codeprism://symbols/functions" => {
461                // All function symbols
462                let functions = server
463                    .graph_store()
464                    .get_nodes_by_kind(codeprism_core::NodeKind::Function);
465                let functions_json = serde_json::json!(functions
466                    .iter()
467                    .map(|node| { self.create_node_info_with_context(node, 5) })
468                    .collect::<Vec<_>>());
469
470                Ok(ResourceContent {
471                    uri: uri.to_string(),
472                    mime_type: Some("application/json".to_string()),
473                    text: Some(serde_json::to_string_pretty(&functions_json)?),
474                    blob: None,
475                })
476            }
477
478            "codeprism://symbols/classes" => {
479                // All class symbols
480                let classes = server
481                    .graph_store()
482                    .get_nodes_by_kind(codeprism_core::NodeKind::Class);
483                let classes_json = serde_json::json!(classes
484                    .iter()
485                    .map(|node| { self.create_node_info_with_context(node, 5) })
486                    .collect::<Vec<_>>());
487
488                Ok(ResourceContent {
489                    uri: uri.to_string(),
490                    mime_type: Some("application/json".to_string()),
491                    text: Some(serde_json::to_string_pretty(&classes_json)?),
492                    blob: None,
493                })
494            }
495
496            "codeprism://symbols/variables" => {
497                // All variable symbols
498                let variables = server
499                    .graph_store()
500                    .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
501                let variables_json = serde_json::json!(variables
502                    .iter()
503                    .map(|node| { self.create_node_info_with_context(node, 5) })
504                    .collect::<Vec<_>>());
505
506                Ok(ResourceContent {
507                    uri: uri.to_string(),
508                    mime_type: Some("application/json".to_string()),
509                    text: Some(serde_json::to_string_pretty(&variables_json)?),
510                    blob: None,
511                })
512            }
513
514            "codeprism://symbols/modules" => {
515                // All module symbols
516                let modules = server
517                    .graph_store()
518                    .get_nodes_by_kind(codeprism_core::NodeKind::Module);
519                let modules_json = serde_json::json!(modules
520                    .iter()
521                    .map(|node| { self.create_node_info_with_context(node, 5) })
522                    .collect::<Vec<_>>());
523
524                Ok(ResourceContent {
525                    uri: uri.to_string(),
526                    mime_type: Some("application/json".to_string()),
527                    text: Some(serde_json::to_string_pretty(&modules_json)?),
528                    blob: None,
529                })
530            }
531
532            "codeprism://metrics/quality_dashboard" => {
533                // Quality metrics dashboard
534                let graph_stats = server.graph_store().get_stats();
535                let functions = server
536                    .graph_store()
537                    .get_nodes_by_kind(codeprism_core::NodeKind::Function);
538                let classes = server
539                    .graph_store()
540                    .get_nodes_by_kind(codeprism_core::NodeKind::Class);
541
542                // Calculate basic quality metrics
543                let total_functions = functions.len();
544                let total_classes = classes.len();
545                let average_file_nodes = if graph_stats.total_files > 0 {
546                    graph_stats.total_nodes as f64 / graph_stats.total_files as f64
547                } else {
548                    0.0
549                };
550
551                // Estimate complexity distribution (simplified)
552                let complexity_distribution = serde_json::json!({
553                    "low": (total_functions as f64 * 0.6) as usize,
554                    "medium": (total_functions as f64 * 0.3) as usize,
555                    "high": (total_functions as f64 * 0.1) as usize
556                });
557
558                // Generate technical debt indicators
559                let technical_debt = serde_json::json!({
560                    "large_functions": functions.iter().filter(|f| f.span.len() > 100).count(),
561                    "files_with_many_functions": "estimated_based_on_node_distribution",
562                    "potential_duplicates": "requires_duplicate_analysis",
563                    "complex_classes": classes.iter().filter(|c| {
564                        server.graph_store().get_outgoing_edges(&c.id).len() > 15
565                    }).count()
566                });
567
568                // Overall quality score (simplified calculation)
569                let maintainability_score = if total_functions > 0 {
570                    let large_function_ratio =
571                        technical_debt["large_functions"].as_u64().unwrap_or(0) as f64
572                            / total_functions as f64;
573                    let complex_class_ratio =
574                        technical_debt["complex_classes"].as_u64().unwrap_or(0) as f64
575                            / total_classes.max(1) as f64;
576                    ((1.0 - large_function_ratio * 0.5 - complex_class_ratio * 0.3) * 100.0)
577                        .max(0.0)
578                } else {
579                    100.0
580                };
581
582                let quality_json = serde_json::json!({
583                    "repository_overview": {
584                        "total_files": graph_stats.total_files,
585                        "total_nodes": graph_stats.total_nodes,
586                        "total_edges": graph_stats.total_edges,
587                        "average_nodes_per_file": average_file_nodes
588                    },
589                    "code_structure": {
590                        "functions": total_functions,
591                        "classes": total_classes,
592                        "modules": graph_stats.nodes_by_kind.get(&codeprism_core::NodeKind::Module).unwrap_or(&0),
593                        "variables": graph_stats.nodes_by_kind.get(&codeprism_core::NodeKind::Variable).unwrap_or(&0)
594                    },
595                    "complexity_distribution": complexity_distribution,
596                    "technical_debt": technical_debt,
597                    "quality_scores": {
598                        "overall": ((maintainability_score + 70.0) / 2.0).clamp(0.0, 100.0),
599                        "maintainability": maintainability_score,
600                        "readability": 75.0
601                    },
602                    "recommendations": [
603                        "Consider refactoring functions longer than 100 lines",
604                        "Review classes with more than 15 methods for single responsibility principle",
605                        "Use analyze_complexity tool for detailed complexity metrics",
606                        "Use detect_patterns tool to identify architectural patterns"
607                    ]
608                });
609
610                Ok(ResourceContent {
611                    uri: uri.to_string(),
612                    mime_type: Some("application/json".to_string()),
613                    text: Some(serde_json::to_string_pretty(&quality_json)?),
614                    blob: None,
615                })
616            }
617
618            "codeprism://architecture/layers" => {
619                // Architectural layer analysis
620                let classes = server
621                    .graph_store()
622                    .get_nodes_by_kind(codeprism_core::NodeKind::Class);
623                let functions = server
624                    .graph_store()
625                    .get_nodes_by_kind(codeprism_core::NodeKind::Function);
626
627                // Identify potential layers based on naming conventions and structure
628                let mut layers = std::collections::HashMap::new();
629
630                // Presentation layer
631                let presentation_classes = classes
632                    .iter()
633                    .filter(|c| {
634                        let name_lower = c.name.to_lowercase();
635                        name_lower.contains("controller")
636                            || name_lower.contains("view")
637                            || name_lower.contains("ui")
638                            || name_lower.contains("component")
639                            || c.file.to_string_lossy().contains("view")
640                            || c.file.to_string_lossy().contains("ui")
641                    })
642                    .count();
643
644                // Business/Service layer
645                let business_classes = classes
646                    .iter()
647                    .filter(|c| {
648                        let name_lower = c.name.to_lowercase();
649                        name_lower.contains("service")
650                            || name_lower.contains("business")
651                            || name_lower.contains("logic")
652                            || name_lower.contains("manager")
653                            || c.file.to_string_lossy().contains("service")
654                            || c.file.to_string_lossy().contains("business")
655                    })
656                    .count();
657
658                // Data access layer
659                let data_classes = classes
660                    .iter()
661                    .filter(|c| {
662                        let name_lower = c.name.to_lowercase();
663                        name_lower.contains("repository")
664                            || name_lower.contains("dao")
665                            || name_lower.contains("data")
666                            || name_lower.contains("model")
667                            || c.file.to_string_lossy().contains("repository")
668                            || c.file.to_string_lossy().contains("model")
669                    })
670                    .count();
671
672                // Infrastructure layer
673                let infrastructure_classes = classes
674                    .iter()
675                    .filter(|c| {
676                        let name_lower = c.name.to_lowercase();
677                        name_lower.contains("config")
678                            || name_lower.contains("util")
679                            || name_lower.contains("helper")
680                            || name_lower.contains("infrastructure")
681                            || c.file.to_string_lossy().contains("config")
682                            || c.file.to_string_lossy().contains("util")
683                    })
684                    .count();
685
686                layers.insert("presentation", presentation_classes);
687                layers.insert("business", business_classes);
688                layers.insert("data", data_classes);
689                layers.insert("infrastructure", infrastructure_classes);
690
691                // Directory structure analysis
692                let all_files = server.graph_store().get_all_files();
693                let mut directory_layers = std::collections::HashMap::new();
694
695                for file in &all_files {
696                    if let Some(parent) = file.parent() {
697                        let dir_name = parent.file_name().and_then(|n| n.to_str()).unwrap_or("");
698                        *directory_layers.entry(dir_name.to_string()).or_insert(0) += 1;
699                    }
700                }
701
702                let layers_json = serde_json::json!({
703                    "layer_analysis": {
704                        "presentation_layer": {
705                            "classes": presentation_classes,
706                            "description": "Controllers, views, UI components"
707                        },
708                        "business_layer": {
709                            "classes": business_classes,
710                            "description": "Business logic, services, managers"
711                        },
712                        "data_layer": {
713                            "classes": data_classes,
714                            "description": "Repositories, DAOs, data models"
715                        },
716                        "infrastructure_layer": {
717                            "classes": infrastructure_classes,
718                            "description": "Configuration, utilities, infrastructure"
719                        }
720                    },
721                    "directory_structure": directory_layers,
722                    "total_classes": classes.len(),
723                    "total_functions": functions.len(),
724                    "layering_assessment": {
725                        "well_layered": presentation_classes > 0 && business_classes > 0 && data_classes > 0,
726                        "dominant_layer": layers.iter().max_by_key(|(_, count)| *count).map(|(name, _)| *name).unwrap_or("unclear"),
727                        "architectural_style": if presentation_classes > 0 && business_classes > 0 && data_classes > 0 {
728                            "Layered Architecture"
729                        } else {
730                            "Unclear or Monolithic"
731                        }
732                    }
733                });
734
735                Ok(ResourceContent {
736                    uri: uri.to_string(),
737                    mime_type: Some("application/json".to_string()),
738                    text: Some(serde_json::to_string_pretty(&layers_json)?),
739                    blob: None,
740                })
741            }
742
743            "codeprism://architecture/patterns" => {
744                // Design pattern detection (simplified version for resource)
745                let classes = server
746                    .graph_store()
747                    .get_nodes_by_kind(codeprism_core::NodeKind::Class);
748                let mut detected_patterns = Vec::new();
749
750                // Singleton pattern detection
751                for class in &classes {
752                    let methods = server.graph_store().get_outgoing_edges(&class.id);
753                    let has_get_instance = methods.iter().any(|edge| {
754                        if let Some(target_node) = server.graph_store().get_node(&edge.target) {
755                            target_node.name.to_lowercase().contains("getinstance")
756                                || target_node.name.to_lowercase().contains("get_instance")
757                        } else {
758                            false
759                        }
760                    });
761
762                    if has_get_instance {
763                        detected_patterns.push(serde_json::json!({
764                            "pattern": "Singleton",
765                            "class": class.name,
766                            "file": class.file.display().to_string(),
767                            "confidence": "medium"
768                        }));
769                    }
770                }
771
772                // Factory pattern detection
773                let factory_classes = classes
774                    .iter()
775                    .filter(|c| c.name.to_lowercase().contains("factory"))
776                    .map(|c| {
777                        serde_json::json!({
778                            "pattern": "Factory",
779                            "class": c.name,
780                            "file": c.file.display().to_string(),
781                            "confidence": "high"
782                        })
783                    })
784                    .collect::<Vec<_>>();
785
786                detected_patterns.extend(factory_classes);
787
788                // MVC pattern detection
789                let controllers = classes
790                    .iter()
791                    .filter(|c| c.name.to_lowercase().contains("controller"))
792                    .count();
793                let models = classes
794                    .iter()
795                    .filter(|c| c.name.to_lowercase().contains("model"))
796                    .count();
797                let views = classes
798                    .iter()
799                    .filter(|c| c.name.to_lowercase().contains("view"))
800                    .count();
801
802                if controllers > 0 && models > 0 && views > 0 {
803                    detected_patterns.push(serde_json::json!({
804                        "pattern": "MVC (Model-View-Controller)",
805                        "components": {
806                            "controllers": controllers,
807                            "models": models,
808                            "views": views
809                        },
810                        "confidence": "high"
811                    }));
812                }
813
814                let patterns_json = serde_json::json!({
815                    "detected_patterns": detected_patterns,
816                    "pattern_summary": {
817                        "total_patterns": detected_patterns.len(),
818                        "design_patterns": detected_patterns.iter().filter(|p|
819                            p["pattern"].as_str().unwrap_or("") != "MVC (Model-View-Controller)"
820                        ).count(),
821                        "architectural_patterns": if controllers > 0 && models > 0 && views > 0 { 1 } else { 0 }
822                    },
823                    "recommendations": [
824                        "Use detect_patterns tool for detailed pattern analysis",
825                        "Consider implementing missing patterns for better architecture",
826                        "Review pattern implementations for best practices"
827                    ]
828                });
829
830                Ok(ResourceContent {
831                    uri: uri.to_string(),
832                    mime_type: Some("application/json".to_string()),
833                    text: Some(serde_json::to_string_pretty(&patterns_json)?),
834                    blob: None,
835                })
836            }
837
838            "codeprism://architecture/dependencies" => {
839                // High-level dependency analysis
840                let graph_stats = server.graph_store().get_stats();
841                let files = server.graph_store().get_all_files();
842
843                // Calculate dependency metrics
844                let mut file_dependencies = std::collections::HashMap::new();
845                let mut total_dependencies = 0;
846
847                for file in &files {
848                    let nodes = server.graph_store().get_nodes_in_file(file);
849                    let mut file_dep_count = 0;
850
851                    for node in nodes {
852                        let outgoing = server.graph_store().get_outgoing_edges(&node.id);
853                        file_dep_count += outgoing.len();
854                        total_dependencies += outgoing.len();
855                    }
856
857                    if let Some(file_name) = file.file_name().and_then(|n| n.to_str()) {
858                        file_dependencies.insert(file_name.to_string(), file_dep_count);
859                    }
860                }
861
862                // Find highly coupled files
863                let average_dependencies = if !files.is_empty() {
864                    total_dependencies as f64 / files.len() as f64
865                } else {
866                    0.0
867                };
868
869                let highly_coupled_files: Vec<_> = file_dependencies
870                    .iter()
871                    .filter(|(_, &count)| count as f64 > average_dependencies * 1.5)
872                    .map(|(name, count)| {
873                        serde_json::json!({
874                            "file": name,
875                            "dependencies": count
876                        })
877                    })
878                    .collect();
879
880                // Identify potential dependency cycles (simplified)
881                let import_edges = graph_stats.total_edges; // Simplified - would need more detailed analysis
882                let potential_cycles = if import_edges > graph_stats.total_nodes {
883                    (import_edges as f64 - graph_stats.total_nodes as f64).max(0.0) as usize
884                } else {
885                    0
886                };
887
888                let dependencies_json = serde_json::json!({
889                    "dependency_overview": {
890                        "total_files": files.len(),
891                        "total_dependencies": total_dependencies,
892                        "average_dependencies_per_file": average_dependencies,
893                        "highly_coupled_files": highly_coupled_files.len()
894                    },
895                    "coupling_analysis": {
896                        "files_above_average": file_dependencies.iter()
897                            .filter(|(_, &count)| count as f64 > average_dependencies)
898                            .count(),
899                        "max_dependencies": file_dependencies.values().max().unwrap_or(&0),
900                        "min_dependencies": file_dependencies.values().min().unwrap_or(&0)
901                    },
902                    "highly_coupled_files": highly_coupled_files,
903                    "potential_issues": {
904                        "potential_cycles": potential_cycles,
905                        "coupling_hotspots": highly_coupled_files.len()
906                    },
907                    "recommendations": [
908                        "Use analyze_transitive_dependencies tool for detailed cycle detection",
909                        "Consider refactoring highly coupled files",
910                        "Review dependency chains for optimization opportunities"
911                    ]
912                });
913
914                Ok(ResourceContent {
915                    uri: uri.to_string(),
916                    mime_type: Some("application/json".to_string()),
917                    text: Some(serde_json::to_string_pretty(&dependencies_json)?),
918                    blob: None,
919                })
920            }
921
922            uri if uri.starts_with("codeprism://repository/file/") => {
923                // Individual file content
924                let file_path = uri.strip_prefix("codeprism://repository/file/").unwrap();
925                let full_path = repo_path.join(file_path);
926
927                if !full_path.exists() {
928                    return Err(anyhow::anyhow!("File not found: {}", file_path));
929                }
930
931                let content = std::fs::read_to_string(&full_path)
932                    .map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", file_path, e))?;
933
934                Ok(ResourceContent {
935                    uri: uri.to_string(),
936                    mime_type: Some(detect_mime_type(&full_path)),
937                    text: Some(content),
938                    blob: None,
939                })
940            }
941
942            _ => Err(anyhow::anyhow!("Unknown resource URI: {}", uri)),
943        }
944    }
945}
946
947/// Detect MIME type based on file extension
948fn detect_mime_type(path: &Path) -> String {
949    match path.extension().and_then(|ext| ext.to_str()) {
950        Some("js") => "application/javascript".to_string(),
951        Some("ts") => "application/typescript".to_string(),
952        Some("py") => "text/x-python".to_string(),
953        Some("java") => "text/x-java-source".to_string(),
954        Some("json") => "application/json".to_string(),
955        Some("md") => "text/markdown".to_string(),
956        Some("txt") => "text/plain".to_string(),
957        Some("html") => "text/html".to_string(),
958        Some("css") => "text/css".to_string(),
959        Some("xml") => "application/xml".to_string(),
960        Some("yaml") | Some("yml") => "application/yaml".to_string(),
961        Some("toml") => "application/toml".to_string(),
962        _ => "text/plain".to_string(),
963    }
964}
965
966#[cfg(test)]
967mod tests {
968    use super::*;
969    use std::fs;
970    use tempfile::TempDir;
971
972    #[tokio::test]
973    async fn test_resource_capabilities() {
974        let capabilities = ResourceCapabilities {
975            subscribe: Some(true),
976            list_changed: Some(true),
977        };
978
979        assert_eq!(capabilities.subscribe, Some(true));
980        assert_eq!(capabilities.list_changed, Some(true));
981    }
982
983    #[test]
984    fn test_resource_serialization() {
985        let resource = Resource {
986            uri: "codeprism://repository/test.py".to_string(),
987            name: Some("test.py".to_string()),
988            description: Some("A Python test file".to_string()),
989            mime_type: Some("text/x-python".to_string()),
990        };
991
992        let json = serde_json::to_string(&resource).unwrap();
993        let deserialized: Resource = serde_json::from_str(&json).unwrap();
994
995        assert_eq!(resource.uri, deserialized.uri);
996        assert_eq!(resource.name, deserialized.name);
997        assert_eq!(resource.description, deserialized.description);
998        assert_eq!(resource.mime_type, deserialized.mime_type);
999    }
1000
1001    #[test]
1002    fn test_mime_type_detection() {
1003        assert_eq!(
1004            detect_mime_type(Path::new("test.js")),
1005            "application/javascript"
1006        );
1007        assert_eq!(detect_mime_type(Path::new("test.py")), "text/x-python");
1008        assert_eq!(
1009            detect_mime_type(Path::new("test.java")),
1010            "text/x-java-source"
1011        );
1012        assert_eq!(detect_mime_type(Path::new("test.unknown")), "text/plain");
1013    }
1014
1015    async fn create_test_server() -> crate::CodePrismMcpServer {
1016        let temp_dir = TempDir::new().expect("Failed to create temp dir");
1017        let repo_path = temp_dir.path();
1018
1019        // Create test files for comprehensive resource testing
1020        fs::write(
1021            repo_path.join("main.py"),
1022            r#"
1023class Application:
1024    """Main application class."""
1025    
1026    def __init__(self, name: str):
1027        self.name = name
1028        self.users = []
1029    
1030    def add_user(self, user: 'User') -> None:
1031        """Add a user to the application."""
1032        self.users.append(user)
1033    
1034    def run(self) -> None:
1035        """Run the application."""
1036        print(f"Running {self.name}")
1037
1038class User:
1039    """User class representing a system user."""
1040    
1041    def __init__(self, username: str, email: str):
1042        self.username = username
1043        self.email = email
1044    
1045    def get_display_name(self) -> str:
1046        """Get the display name for the user."""
1047        return f"{self.username} ({self.email})"
1048
1049def create_app() -> Application:
1050    """Create and configure the application."""
1051    app = Application("MyApp")
1052    return app
1053
1054if __name__ == "__main__":
1055    app = create_app()
1056    user = User("alice", "alice@example.com")
1057    app.add_user(user)
1058    app.run()
1059"#,
1060        )
1061        .unwrap();
1062
1063        fs::write(
1064            repo_path.join("utils.py"),
1065            r#"
1066"""Utility functions for the application."""
1067
1068import os
1069import json
1070from typing import Dict, Any, List, Optional
1071
1072def load_config(config_path: str) -> Dict[str, Any]:
1073    """Load configuration from a JSON file."""
1074    if not os.path.exists(config_path):
1075        return {}
1076    
1077    with open(config_path, 'r') as f:
1078        return json.load(f)
1079
1080def validate_email(email: str) -> bool:
1081    """Simple email validation."""
1082    return '@' in email and '.' in email
1083
1084def format_user_list(users: List['User']) -> str:
1085    """Format a list of users for display."""
1086    if not users:
1087        return "No users"
1088    
1089    return ', '.join(user.get_display_name() for user in users)
1090
1091class ConfigManager:
1092    """Manages application configuration."""
1093    
1094    def __init__(self, config_path: str):
1095        self.config_path = config_path
1096        self.config = load_config(config_path)
1097    
1098    def get(self, key: str, default: Any = None) -> Any:
1099        """Get a configuration value."""
1100        return self.config.get(key, default)
1101    
1102    def set(self, key: str, value: Any) -> None:
1103        """Set a configuration value."""
1104        self.config[key] = value
1105"#,
1106        )
1107        .unwrap();
1108
1109        fs::write(
1110            repo_path.join("constants.py"),
1111            r#"
1112"""Application constants."""
1113
1114# Database configuration
1115DATABASE_URL = "sqlite:///app.db"
1116MAX_CONNECTIONS = 10
1117
1118# User limits
1119MAX_USERNAME_LENGTH = 50
1120MAX_EMAIL_LENGTH = 100
1121
1122# Application settings
1123APP_NAME = "MyApplication"
1124VERSION = "1.0.0"
1125DEBUG = False
1126
1127# Feature flags
1128ENABLE_LOGGING = True
1129ENABLE_METRICS = False
1130ENABLE_CACHE = True
1131"#,
1132        )
1133        .unwrap();
1134
1135        let mut server = crate::CodePrismMcpServer::new().expect("Failed to create server");
1136        server
1137            .initialize_with_repository(repo_path)
1138            .await
1139            .expect("Failed to initialize repository");
1140
1141        // Keep temp_dir alive
1142        std::mem::forget(temp_dir);
1143
1144        server
1145    }
1146
1147    #[tokio::test]
1148    async fn test_resource_manager_creation() {
1149        let server = create_test_server().await;
1150        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1151        let _resource_manager = ResourceManager::new(server_arc);
1152
1153        // Resource manager should be created successfully
1154    }
1155
1156    #[tokio::test]
1157    async fn test_list_resources_with_repository() {
1158        let server = create_test_server().await;
1159        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1160        let resource_manager = ResourceManager::new(server_arc);
1161
1162        let params = ListResourcesParams { cursor: None };
1163        let result = resource_manager.list_resources(params).await;
1164        assert!(result.is_ok());
1165
1166        let resources_result = result.unwrap();
1167        assert!(!resources_result.resources.is_empty());
1168        assert!(resources_result.next_cursor.is_none());
1169
1170        // Verify we have the expected resource types
1171        let resource_uris: Vec<String> = resources_result
1172            .resources
1173            .iter()
1174            .map(|r| r.uri.clone())
1175            .collect();
1176
1177        // Should have repository resources
1178        assert!(resource_uris
1179            .iter()
1180            .any(|uri| uri == "codeprism://repository/"));
1181        assert!(resource_uris
1182            .iter()
1183            .any(|uri| uri == "codeprism://repository/stats"));
1184        assert!(resource_uris
1185            .iter()
1186            .any(|uri| uri == "codeprism://repository/config"));
1187        assert!(resource_uris
1188            .iter()
1189            .any(|uri| uri == "codeprism://repository/tree"));
1190
1191        // Should have graph resources
1192        assert!(resource_uris
1193            .iter()
1194            .any(|uri| uri == "codeprism://graph/repository"));
1195
1196        // Should have symbol resources
1197        assert!(resource_uris
1198            .iter()
1199            .any(|uri| uri == "codeprism://symbols/functions"));
1200        assert!(resource_uris
1201            .iter()
1202            .any(|uri| uri == "codeprism://symbols/classes"));
1203        assert!(resource_uris
1204            .iter()
1205            .any(|uri| uri == "codeprism://symbols/variables"));
1206        assert!(resource_uris
1207            .iter()
1208            .any(|uri| uri == "codeprism://symbols/modules"));
1209
1210        // Should have file resources
1211        assert!(resource_uris.iter().any(|uri| uri.contains("main.py")));
1212        assert!(resource_uris.iter().any(|uri| uri.contains("utils.py")));
1213        assert!(resource_uris.iter().any(|uri| uri.contains("constants.py")));
1214    }
1215
1216    #[tokio::test]
1217    async fn test_read_repository_root_resource() {
1218        let server = create_test_server().await;
1219        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1220        let resource_manager = ResourceManager::new(server_arc);
1221
1222        let params = ReadResourceParams {
1223            uri: "codeprism://repository/".to_string(),
1224        };
1225
1226        let result = resource_manager.read_resource(params).await;
1227        assert!(result.is_ok());
1228
1229        let read_result = result.unwrap();
1230        assert_eq!(read_result.contents.len(), 1);
1231
1232        let content = &read_result.contents[0];
1233        assert_eq!(content.uri, "codeprism://repository/");
1234        assert_eq!(content.mime_type, Some("application/json".to_string()));
1235        assert!(content.text.is_some());
1236
1237        let info: serde_json::Value = serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1238        assert!(info["path"].is_string());
1239        assert_eq!(info["type"].as_str().unwrap(), "repository_root");
1240    }
1241
1242    #[tokio::test]
1243    async fn test_read_repository_stats_resource() {
1244        let server = create_test_server().await;
1245        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1246        let resource_manager = ResourceManager::new(server_arc);
1247
1248        let params = ReadResourceParams {
1249            uri: "codeprism://repository/stats".to_string(),
1250        };
1251
1252        let result = resource_manager.read_resource(params).await;
1253        assert!(result.is_ok());
1254
1255        let read_result = result.unwrap();
1256        assert_eq!(read_result.contents.len(), 1);
1257
1258        let content = &read_result.contents[0];
1259        assert_eq!(content.uri, "codeprism://repository/stats");
1260        assert_eq!(content.mime_type, Some("application/json".to_string()));
1261        assert!(content.text.is_some());
1262
1263        let stats: serde_json::Value =
1264            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1265        assert!(stats["total_files"].is_number());
1266        assert!(stats["total_nodes"].is_number());
1267        assert!(stats["total_edges"].is_number());
1268    }
1269
1270    #[tokio::test]
1271    async fn test_read_repository_config_resource() {
1272        let server = create_test_server().await;
1273        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1274        let resource_manager = ResourceManager::new(server_arc);
1275
1276        let params = ReadResourceParams {
1277            uri: "codeprism://repository/config".to_string(),
1278        };
1279
1280        let result = resource_manager.read_resource(params).await;
1281        assert!(result.is_ok());
1282
1283        let read_result = result.unwrap();
1284        let content = &read_result.contents[0];
1285
1286        let config: serde_json::Value =
1287            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1288        assert!(config["path"].is_string());
1289        assert!(config["scanner_config"].is_object());
1290        assert!(config["scanner_config"]["supported_extensions"].is_array());
1291    }
1292
1293    #[tokio::test]
1294    async fn test_read_file_tree_resource() {
1295        let server = create_test_server().await;
1296        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1297        let resource_manager = ResourceManager::new(server_arc);
1298
1299        let params = ReadResourceParams {
1300            uri: "codeprism://repository/tree".to_string(),
1301        };
1302
1303        let result = resource_manager.read_resource(params).await;
1304        assert!(result.is_ok());
1305
1306        let read_result = result.unwrap();
1307        let content = &read_result.contents[0];
1308
1309        let tree: serde_json::Value = serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1310        assert!(tree["files"].is_array());
1311        assert!(tree["total_count"].is_number());
1312
1313        let files = tree["files"].as_array().unwrap();
1314        assert!(files
1315            .iter()
1316            .any(|f| f.as_str().unwrap().contains("main.py")));
1317        assert!(files
1318            .iter()
1319            .any(|f| f.as_str().unwrap().contains("utils.py")));
1320    }
1321
1322    #[tokio::test]
1323    async fn test_read_graph_repository_resource() {
1324        let server = create_test_server().await;
1325        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1326        let resource_manager = ResourceManager::new(server_arc);
1327
1328        let params = ReadResourceParams {
1329            uri: "codeprism://graph/repository".to_string(),
1330        };
1331
1332        let result = resource_manager.read_resource(params).await;
1333        assert!(result.is_ok());
1334
1335        let read_result = result.unwrap();
1336        let content = &read_result.contents[0];
1337
1338        let graph: serde_json::Value =
1339            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1340        assert!(graph["nodes"].is_number());
1341        assert!(graph["edges"].is_number());
1342        assert!(graph["files"].is_number());
1343        assert!(graph["nodes_by_kind"].is_object());
1344        assert!(graph["last_updated"].is_number());
1345    }
1346
1347    #[tokio::test]
1348    async fn test_read_symbols_functions_resource() {
1349        let server = create_test_server().await;
1350        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1351        let resource_manager = ResourceManager::new(server_arc);
1352
1353        let params = ReadResourceParams {
1354            uri: "codeprism://symbols/functions".to_string(),
1355        };
1356
1357        let result = resource_manager.read_resource(params).await;
1358        assert!(result.is_ok());
1359
1360        let read_result = result.unwrap();
1361        let content = &read_result.contents[0];
1362
1363        let functions: serde_json::Value =
1364            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1365        assert!(functions.is_array());
1366
1367        // Check structure of function entries
1368        if let Some(first_function) = functions.as_array().unwrap().first() {
1369            assert!(first_function["id"].is_string());
1370            assert!(first_function["name"].is_string());
1371            assert!(first_function["file"].is_string());
1372            assert!(first_function["span"].is_object());
1373            assert!(first_function["language"].is_string());
1374        }
1375    }
1376
1377    #[tokio::test]
1378    async fn test_read_symbols_classes_resource() {
1379        let server = create_test_server().await;
1380        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1381        let resource_manager = ResourceManager::new(server_arc);
1382
1383        let params = ReadResourceParams {
1384            uri: "codeprism://symbols/classes".to_string(),
1385        };
1386
1387        let result = resource_manager.read_resource(params).await;
1388        assert!(result.is_ok());
1389
1390        let read_result = result.unwrap();
1391        let content = &read_result.contents[0];
1392
1393        let classes: serde_json::Value =
1394            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1395        assert!(classes.is_array());
1396    }
1397
1398    #[tokio::test]
1399    async fn test_read_file_resource() {
1400        let server = create_test_server().await;
1401        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1402        let resource_manager = ResourceManager::new(server_arc);
1403
1404        let params = ReadResourceParams {
1405            uri: "codeprism://repository/file/main.py".to_string(),
1406        };
1407
1408        let result = resource_manager.read_resource(params).await;
1409        assert!(result.is_ok());
1410
1411        let read_result = result.unwrap();
1412        let content = &read_result.contents[0];
1413
1414        assert_eq!(content.uri, "codeprism://repository/file/main.py");
1415        assert_eq!(content.mime_type, Some("text/x-python".to_string()));
1416        assert!(content.text.is_some());
1417
1418        let file_content = content.text.as_ref().unwrap();
1419        assert!(file_content.contains("class Application"));
1420        assert!(file_content.contains("class User"));
1421        assert!(file_content.contains("def create_app"));
1422    }
1423
1424    #[tokio::test]
1425    async fn test_read_nonexistent_file_resource() {
1426        let server = create_test_server().await;
1427        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1428        let resource_manager = ResourceManager::new(server_arc);
1429
1430        let params = ReadResourceParams {
1431            uri: "codeprism://repository/file/nonexistent.py".to_string(),
1432        };
1433
1434        let result = resource_manager.read_resource(params).await;
1435        assert!(result.is_err());
1436
1437        let error = result.unwrap_err();
1438        assert!(error.to_string().contains("File not found"));
1439    }
1440
1441    #[tokio::test]
1442    async fn test_read_unsupported_resource_uri() {
1443        let server = create_test_server().await;
1444        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1445        let resource_manager = ResourceManager::new(server_arc);
1446
1447        let params = ReadResourceParams {
1448            uri: "invalid://unsupported/resource".to_string(),
1449        };
1450
1451        let result = resource_manager.read_resource(params).await;
1452        assert!(result.is_err());
1453
1454        let error = result.unwrap_err();
1455        assert!(error.to_string().contains("Unsupported resource URI"));
1456    }
1457
1458    #[tokio::test]
1459    async fn test_read_unknown_repository_resource() {
1460        let server = create_test_server().await;
1461        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1462        let resource_manager = ResourceManager::new(server_arc);
1463
1464        let params = ReadResourceParams {
1465            uri: "codeprism://repository/unknown_resource".to_string(),
1466        };
1467
1468        let result = resource_manager.read_resource(params).await;
1469        assert!(result.is_err());
1470
1471        let error = result.unwrap_err();
1472        assert!(error.to_string().contains("Unknown resource URI"));
1473    }
1474
1475    #[test]
1476    fn test_resource_content_serialization() {
1477        let content = ResourceContent {
1478            uri: "codeprism://test".to_string(),
1479            mime_type: Some("application/json".to_string()),
1480            text: Some("{}".to_string()),
1481            blob: None,
1482        };
1483
1484        let json = serde_json::to_string(&content).unwrap();
1485        let deserialized: ResourceContent = serde_json::from_str(&json).unwrap();
1486
1487        assert_eq!(content.uri, deserialized.uri);
1488        assert_eq!(content.mime_type, deserialized.mime_type);
1489        assert_eq!(content.text, deserialized.text);
1490        assert_eq!(content.blob, deserialized.blob);
1491    }
1492
1493    #[test]
1494    fn test_list_resources_params_serialization() {
1495        let params = ListResourcesParams {
1496            cursor: Some("test_cursor".to_string()),
1497        };
1498
1499        let json = serde_json::to_string(&params).unwrap();
1500        let deserialized: ListResourcesParams = serde_json::from_str(&json).unwrap();
1501
1502        assert_eq!(params.cursor, deserialized.cursor);
1503    }
1504
1505    #[test]
1506    fn test_read_resource_params_serialization() {
1507        let params = ReadResourceParams {
1508            uri: "codeprism://test".to_string(),
1509        };
1510
1511        let json = serde_json::to_string(&params).unwrap();
1512        let deserialized: ReadResourceParams = serde_json::from_str(&json).unwrap();
1513
1514        assert_eq!(params.uri, deserialized.uri);
1515    }
1516
1517    #[test]
1518    fn test_additional_mime_types() {
1519        assert_eq!(
1520            detect_mime_type(Path::new("config.json")),
1521            "application/json"
1522        );
1523        assert_eq!(detect_mime_type(Path::new("README.md")), "text/markdown");
1524        assert_eq!(detect_mime_type(Path::new("data.xml")), "application/xml");
1525        assert_eq!(
1526            detect_mime_type(Path::new("config.yaml")),
1527            "application/yaml"
1528        );
1529        assert_eq!(
1530            detect_mime_type(Path::new("config.yml")),
1531            "application/yaml"
1532        );
1533        assert_eq!(
1534            detect_mime_type(Path::new("Cargo.toml")),
1535            "application/toml"
1536        );
1537        assert_eq!(detect_mime_type(Path::new("index.html")), "text/html");
1538        assert_eq!(detect_mime_type(Path::new("styles.css")), "text/css");
1539        assert_eq!(detect_mime_type(Path::new("notes.txt")), "text/plain");
1540    }
1541
1542    #[tokio::test]
1543    async fn test_symbol_resources_include_source_context() {
1544        let server = create_test_server().await;
1545        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1546        let resource_manager = ResourceManager::new(server_arc.clone());
1547
1548        // Wait for indexing
1549        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1550
1551        // Test functions resource includes context
1552        let params = ReadResourceParams {
1553            uri: "codeprism://symbols/functions".to_string(),
1554        };
1555
1556        let result = resource_manager.read_resource(params).await;
1557        assert!(result.is_ok());
1558
1559        let read_result = result.unwrap();
1560        let content = &read_result.contents[0];
1561        assert!(content.text.is_some());
1562
1563        let functions: serde_json::Value =
1564            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1565
1566        if let Some(functions_array) = functions.as_array() {
1567            if !functions_array.is_empty() {
1568                let first_function = &functions_array[0];
1569
1570                // Verify basic function info is present
1571                assert!(first_function["id"].is_string());
1572                assert!(first_function["name"].is_string());
1573                assert!(first_function["kind"].is_string());
1574                assert!(first_function["file"].is_string());
1575
1576                // Verify source context is included
1577                assert!(first_function["source_context"].is_object());
1578                assert!(first_function["source_context"]["target_line"].is_number());
1579                assert!(first_function["source_context"]["lines"].is_array());
1580
1581                let lines = first_function["source_context"]["lines"]
1582                    .as_array()
1583                    .unwrap();
1584                assert!(!lines.is_empty());
1585
1586                // Verify target line is marked
1587                let has_target = lines.iter().any(|line| line["is_target"] == true);
1588                assert!(has_target);
1589            }
1590        }
1591    }
1592
1593    #[tokio::test]
1594    async fn test_classes_resource_with_context() {
1595        let server = create_test_server().await;
1596        let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1597        let resource_manager = ResourceManager::new(server_arc);
1598
1599        // Wait for indexing
1600        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1601
1602        let params = ReadResourceParams {
1603            uri: "codeprism://symbols/classes".to_string(),
1604        };
1605
1606        let result = resource_manager.read_resource(params).await;
1607        assert!(result.is_ok());
1608
1609        let read_result = result.unwrap();
1610        let content = &read_result.contents[0];
1611
1612        let classes: serde_json::Value =
1613            serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1614
1615        if let Some(classes_array) = classes.as_array() {
1616            if !classes_array.is_empty() {
1617                let first_class = &classes_array[0];
1618
1619                // Verify source context is included for classes too
1620                assert!(first_class["source_context"].is_object());
1621                assert!(first_class["source_context"]["target_line"].is_number());
1622                assert!(first_class["source_context"]["lines"].is_array());
1623            }
1624        }
1625    }
1626
1627    #[tokio::test]
1628    async fn test_context_extraction_in_resource_manager() {
1629        use std::fs;
1630        use tempfile::TempDir;
1631
1632        let temp_dir = TempDir::new().unwrap();
1633        let test_file = temp_dir.path().join("test.py");
1634
1635        // Create a test file
1636        fs::write(
1637            &test_file,
1638            r#"# Test file
1639def example_function():
1640    """An example function."""
1641    return "hello"
1642"#,
1643        )
1644        .unwrap();
1645
1646        let server = create_test_server().await;
1647        let resource_manager =
1648            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1649
1650        // Test context extraction
1651        let context = resource_manager.extract_source_context(&test_file, 2, 2);
1652        assert!(context.is_some());
1653
1654        let context_value = context.unwrap();
1655        assert_eq!(context_value["target_line"], 2);
1656
1657        let lines = context_value["lines"].as_array().unwrap();
1658        assert!(!lines.is_empty());
1659    }
1660
1661    #[tokio::test]
1662    async fn test_architectural_layers_resource() {
1663        let server = create_test_server().await;
1664        let resource_manager =
1665            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1666
1667        let params = ReadResourceParams {
1668            uri: "codeprism://architecture/layers".to_string(),
1669        };
1670
1671        let result = resource_manager.read_resource(params).await;
1672        assert!(result.is_ok());
1673
1674        let resource_result = result.unwrap();
1675        assert_eq!(resource_result.contents.len(), 1);
1676
1677        let content = &resource_result.contents[0];
1678        assert_eq!(content.uri, "codeprism://architecture/layers");
1679        assert_eq!(content.mime_type, Some("application/json".to_string()));
1680        assert!(content.text.is_some());
1681
1682        // Verify the JSON structure
1683        let json_text = content.text.as_ref().unwrap();
1684        let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1685        assert!(parsed["layer_analysis"].is_object());
1686        assert!(parsed["directory_structure"].is_object());
1687        assert!(parsed["layering_assessment"].is_object());
1688    }
1689
1690    #[tokio::test]
1691    async fn test_architectural_patterns_resource() {
1692        let server = create_test_server().await;
1693        let resource_manager =
1694            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1695
1696        let params = ReadResourceParams {
1697            uri: "codeprism://architecture/patterns".to_string(),
1698        };
1699
1700        let result = resource_manager.read_resource(params).await;
1701        assert!(result.is_ok());
1702
1703        let resource_result = result.unwrap();
1704        assert_eq!(resource_result.contents.len(), 1);
1705
1706        let content = &resource_result.contents[0];
1707        assert_eq!(content.uri, "codeprism://architecture/patterns");
1708        assert!(content.text.is_some());
1709
1710        // Verify the JSON structure
1711        let json_text = content.text.as_ref().unwrap();
1712        let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1713        assert!(parsed["detected_patterns"].is_array());
1714        assert!(parsed["pattern_summary"].is_object());
1715        assert!(parsed["recommendations"].is_array());
1716    }
1717
1718    #[tokio::test]
1719    async fn test_architectural_dependencies_resource() {
1720        let server = create_test_server().await;
1721        let resource_manager =
1722            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1723
1724        let params = ReadResourceParams {
1725            uri: "codeprism://architecture/dependencies".to_string(),
1726        };
1727
1728        let result = resource_manager.read_resource(params).await;
1729        assert!(result.is_ok());
1730
1731        let resource_result = result.unwrap();
1732        assert_eq!(resource_result.contents.len(), 1);
1733
1734        let content = &resource_result.contents[0];
1735        assert_eq!(content.uri, "codeprism://architecture/dependencies");
1736        assert!(content.text.is_some());
1737
1738        // Verify the JSON structure
1739        let json_text = content.text.as_ref().unwrap();
1740        let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1741        assert!(parsed["dependency_overview"].is_object());
1742        assert!(parsed["coupling_analysis"].is_object());
1743        assert!(parsed["potential_issues"].is_object());
1744        assert!(parsed["recommendations"].is_array());
1745    }
1746
1747    #[tokio::test]
1748    async fn test_architectural_resources_in_list() {
1749        let server = create_test_server().await;
1750        let resource_manager =
1751            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1752
1753        let params = ListResourcesParams { cursor: None };
1754        let result = resource_manager.list_resources(params).await;
1755        assert!(result.is_ok());
1756
1757        let resources_result = result.unwrap();
1758        let resource_uris: Vec<&String> =
1759            resources_result.resources.iter().map(|r| &r.uri).collect();
1760
1761        // Check that our new architectural resources are included
1762        assert!(resource_uris.contains(&&"codeprism://architecture/layers".to_string()));
1763        assert!(resource_uris.contains(&&"codeprism://architecture/patterns".to_string()));
1764        assert!(resource_uris.contains(&&"codeprism://architecture/dependencies".to_string()));
1765
1766        // Should have all resources including architectural ones
1767        assert!(resources_result.resources.len() >= 12); // Original + Quality + Architectural
1768    }
1769
1770    #[tokio::test]
1771    async fn test_enhanced_quality_dashboard() {
1772        let server = create_test_server().await;
1773        let resource_manager =
1774            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1775
1776        let params = ReadResourceParams {
1777            uri: "codeprism://metrics/quality_dashboard".to_string(),
1778        };
1779
1780        let result = resource_manager.read_resource(params).await;
1781        assert!(result.is_ok());
1782
1783        let resource_result = result.unwrap();
1784        let content = &resource_result.contents[0];
1785        let json_text = content.text.as_ref().unwrap();
1786        let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1787
1788        // Verify enhanced structure
1789        assert!(parsed["repository_overview"].is_object());
1790        assert!(parsed["code_structure"].is_object());
1791        assert!(parsed["complexity_distribution"].is_object());
1792        assert!(parsed["technical_debt"].is_object());
1793        assert!(parsed["quality_scores"].is_object());
1794        assert!(parsed["recommendations"].is_array());
1795
1796        // Verify quality scores
1797        let quality_scores = &parsed["quality_scores"];
1798        assert!(quality_scores["overall"].is_number());
1799        assert!(quality_scores["maintainability"].is_number());
1800        assert!(quality_scores["readability"].is_number());
1801    }
1802
1803    #[tokio::test]
1804    async fn test_architectural_resource_error_handling() {
1805        let server = create_test_server().await;
1806        let resource_manager =
1807            ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1808
1809        // Test with invalid architectural resource URI
1810        let params = ReadResourceParams {
1811            uri: "codeprism://architecture/invalid".to_string(),
1812        };
1813
1814        let result = resource_manager.read_resource(params).await;
1815        assert!(result.is_err()); // Should return error for unsupported URI
1816    }
1817}