codeprism_mcp/
lib.rs

1//! # CodePrism MCP Server
2//!
3//! A Model Context Protocol (MCP) compliant server that provides access to code repositories
4//! through standardized Resources, Tools, and Prompts.
5//!
6//! This implementation follows the MCP specification for JSON-RPC 2.0 communication
7//! over stdio transport, enabling integration with MCP clients like Claude Desktop,
8//! Cursor, and other AI applications.
9
10use anyhow::Result;
11
12use codeprism_core::{
13    ast::{Edge, Language, Node},
14    graph::{GraphQuery, GraphStore},
15    indexer::BulkIndexer,
16    parser::{LanguageParser, ParseContext, ParseResult, ParserEngine},
17    repository::RepositoryManager,
18    scanner::RepositoryScanner,
19};
20use std::collections::HashMap;
21use std::path::Path;
22use std::sync::Arc;
23
24pub mod config; // Phase 2.2: Advanced configuration system
25pub mod context;
26pub mod error_handler;
27pub mod monitoring; // Phase 2.2: Performance monitoring system
28pub mod prompts;
29pub mod protocol;
30pub mod resources;
31pub mod server;
32pub mod tools;
33pub mod tools_legacy;
34pub mod transport;
35pub mod validation; // Phase 2.2: Configuration validation & health checks
36
37// Re-export main types
38pub use error_handler::{McpError, McpErrorHandler, McpResult};
39pub use protocol::{
40    InitializeParams, InitializeResult, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse,
41    ServerCapabilities,
42};
43pub use server::McpServer;
44pub use transport::{StdioTransport, Transport};
45
46// Re-export Phase 2.2 types
47pub use config::{
48    CachingConfig, ConfigProfileManager, McpConfigProfile, MonitoringConfig, SecurityConfig,
49};
50pub use monitoring::{MonitoringMiddleware, PerformanceMonitor, PerformanceSummary};
51pub use tools::dynamic_enablement::{DynamicToolManager, RepositoryAnalysis};
52pub use validation::{StartupReport, SystemValidator, ValidationResult};
53
54/// Python language parser adapter
55struct PythonParserAdapter;
56
57impl LanguageParser for PythonParserAdapter {
58    fn language(&self) -> Language {
59        Language::Python
60    }
61
62    fn parse(&self, context: &ParseContext) -> codeprism_core::error::Result<ParseResult> {
63        // Use the Python parser from codeprism-lang-python
64        let python_parser = codeprism_lang_python::PythonLanguageParser::new();
65
66        match codeprism_lang_python::parse_file(
67            &python_parser,
68            &context.repo_id,
69            context.file_path.clone(),
70            context.content.clone(),
71            context.old_tree.clone(),
72        ) {
73            Ok((tree, py_nodes, py_edges)) => {
74                // Convert Python parser types to codeprism types
75                let nodes: Vec<Node> = py_nodes
76                    .into_iter()
77                    .map(|py_node| {
78                        // Convert NodeKind
79                        let codeprism_kind = match py_node.kind {
80                            codeprism_lang_python::NodeKind::Function => {
81                                codeprism_core::ast::NodeKind::Function
82                            }
83                            codeprism_lang_python::NodeKind::Class => {
84                                codeprism_core::ast::NodeKind::Class
85                            }
86                            codeprism_lang_python::NodeKind::Variable => {
87                                codeprism_core::ast::NodeKind::Variable
88                            }
89                            codeprism_lang_python::NodeKind::Module => {
90                                codeprism_core::ast::NodeKind::Module
91                            }
92                            codeprism_lang_python::NodeKind::Import => {
93                                codeprism_core::ast::NodeKind::Import
94                            }
95                            codeprism_lang_python::NodeKind::Parameter => {
96                                codeprism_core::ast::NodeKind::Parameter
97                            }
98                            codeprism_lang_python::NodeKind::Method => {
99                                codeprism_core::ast::NodeKind::Method
100                            }
101                            codeprism_lang_python::NodeKind::Call => {
102                                codeprism_core::ast::NodeKind::Call
103                            }
104                            codeprism_lang_python::NodeKind::Literal => {
105                                codeprism_core::ast::NodeKind::Literal
106                            }
107                            codeprism_lang_python::NodeKind::Route => {
108                                codeprism_core::ast::NodeKind::Route
109                            }
110                            codeprism_lang_python::NodeKind::SqlQuery => {
111                                codeprism_core::ast::NodeKind::SqlQuery
112                            }
113                            codeprism_lang_python::NodeKind::Event => {
114                                codeprism_core::ast::NodeKind::Event
115                            }
116                            codeprism_lang_python::NodeKind::Unknown => {
117                                codeprism_core::ast::NodeKind::Unknown
118                            }
119                        };
120
121                        // Convert Span
122                        let codeprism_span = codeprism_core::ast::Span::new(
123                            py_node.span.start_byte,
124                            py_node.span.end_byte,
125                            py_node.span.start_line,
126                            py_node.span.end_line,
127                            py_node.span.start_column,
128                            py_node.span.end_column,
129                        );
130
131                        Node::new(
132                            &context.repo_id,
133                            codeprism_kind,
134                            py_node.name,
135                            Language::Python,
136                            context.file_path.clone(),
137                            codeprism_span,
138                        )
139                    })
140                    .collect();
141
142                let edges: Vec<Edge> = py_edges
143                    .into_iter()
144                    .map(|py_edge| {
145                        // Convert EdgeKind
146                        let codeprism_edge_kind = match py_edge.kind {
147                            codeprism_lang_python::EdgeKind::Calls => {
148                                codeprism_core::ast::EdgeKind::Calls
149                            }
150                            codeprism_lang_python::EdgeKind::Reads => {
151                                codeprism_core::ast::EdgeKind::Reads
152                            }
153                            codeprism_lang_python::EdgeKind::Writes => {
154                                codeprism_core::ast::EdgeKind::Writes
155                            }
156                            codeprism_lang_python::EdgeKind::Imports => {
157                                codeprism_core::ast::EdgeKind::Imports
158                            }
159                            codeprism_lang_python::EdgeKind::Emits => {
160                                codeprism_core::ast::EdgeKind::Emits
161                            }
162                            codeprism_lang_python::EdgeKind::RoutesTo => {
163                                codeprism_core::ast::EdgeKind::RoutesTo
164                            }
165                            codeprism_lang_python::EdgeKind::Raises => {
166                                codeprism_core::ast::EdgeKind::Raises
167                            }
168                            codeprism_lang_python::EdgeKind::Extends => {
169                                codeprism_core::ast::EdgeKind::Extends
170                            }
171                            codeprism_lang_python::EdgeKind::Implements => {
172                                codeprism_core::ast::EdgeKind::Implements
173                            }
174                        };
175
176                        // Convert NodeIds by using hex representation
177                        let codecodeprism_source =
178                            codeprism_core::ast::NodeId::from_hex(&py_edge.source.to_hex())
179                                .unwrap();
180                        let codeprism_target =
181                            codeprism_core::ast::NodeId::from_hex(&py_edge.target.to_hex())
182                                .unwrap();
183
184                        Edge::new(codecodeprism_source, codeprism_target, codeprism_edge_kind)
185                    })
186                    .collect();
187
188                Ok(ParseResult { tree, nodes, edges })
189            }
190            Err(e) => Err(codeprism_core::error::Error::parse(
191                &context.file_path,
192                format!("Python parsing failed: {}", e),
193            )),
194        }
195    }
196}
197
198/// JavaScript language parser adapter
199struct JavaScriptParserAdapter;
200
201impl LanguageParser for JavaScriptParserAdapter {
202    fn language(&self) -> Language {
203        Language::JavaScript
204    }
205
206    fn parse(&self, context: &ParseContext) -> codeprism_core::error::Result<ParseResult> {
207        // Use the JavaScript parser from codeprism-lang-js
208        let js_parser = codeprism_lang_js::JavaScriptLanguageParser::new();
209
210        match codeprism_lang_js::parse_file(
211            &js_parser,
212            &context.repo_id,
213            context.file_path.clone(),
214            context.content.clone(),
215            context.old_tree.clone(),
216        ) {
217            Ok((tree, js_nodes, js_edges)) => {
218                // Convert JavaScript parser types to codeprism types
219                let nodes: Vec<Node> = js_nodes
220                    .into_iter()
221                    .map(|js_node| {
222                        // Convert NodeKind
223                        let codeprism_kind = match js_node.kind {
224                            codeprism_lang_js::NodeKind::Function => {
225                                codeprism_core::ast::NodeKind::Function
226                            }
227                            codeprism_lang_js::NodeKind::Class => {
228                                codeprism_core::ast::NodeKind::Class
229                            }
230                            codeprism_lang_js::NodeKind::Variable => {
231                                codeprism_core::ast::NodeKind::Variable
232                            }
233                            codeprism_lang_js::NodeKind::Module => {
234                                codeprism_core::ast::NodeKind::Module
235                            }
236                            codeprism_lang_js::NodeKind::Import => {
237                                codeprism_core::ast::NodeKind::Import
238                            }
239                            codeprism_lang_js::NodeKind::Parameter => {
240                                codeprism_core::ast::NodeKind::Parameter
241                            }
242                            codeprism_lang_js::NodeKind::Method => {
243                                codeprism_core::ast::NodeKind::Method
244                            }
245                            codeprism_lang_js::NodeKind::Call => {
246                                codeprism_core::ast::NodeKind::Call
247                            }
248                            codeprism_lang_js::NodeKind::Literal => {
249                                codeprism_core::ast::NodeKind::Literal
250                            }
251                            codeprism_lang_js::NodeKind::Route => {
252                                codeprism_core::ast::NodeKind::Route
253                            }
254                            codeprism_lang_js::NodeKind::SqlQuery => {
255                                codeprism_core::ast::NodeKind::SqlQuery
256                            }
257                            codeprism_lang_js::NodeKind::Event => {
258                                codeprism_core::ast::NodeKind::Event
259                            }
260                            codeprism_lang_js::NodeKind::Unknown => {
261                                codeprism_core::ast::NodeKind::Unknown
262                            }
263                        };
264
265                        // Convert Span
266                        let codeprism_span = codeprism_core::ast::Span::new(
267                            js_node.span.start_byte,
268                            js_node.span.end_byte,
269                            js_node.span.start_line,
270                            js_node.span.end_line,
271                            js_node.span.start_column,
272                            js_node.span.end_column,
273                        );
274
275                        Node::new(
276                            &context.repo_id,
277                            codeprism_kind,
278                            js_node.name,
279                            Language::JavaScript,
280                            context.file_path.clone(),
281                            codeprism_span,
282                        )
283                    })
284                    .collect();
285
286                let edges: Vec<Edge> = js_edges
287                    .into_iter()
288                    .map(|js_edge| {
289                        // Convert EdgeKind
290                        let codeprism_edge_kind = match js_edge.kind {
291                            codeprism_lang_js::EdgeKind::Calls => {
292                                codeprism_core::ast::EdgeKind::Calls
293                            }
294                            codeprism_lang_js::EdgeKind::Reads => {
295                                codeprism_core::ast::EdgeKind::Reads
296                            }
297                            codeprism_lang_js::EdgeKind::Writes => {
298                                codeprism_core::ast::EdgeKind::Writes
299                            }
300                            codeprism_lang_js::EdgeKind::Imports => {
301                                codeprism_core::ast::EdgeKind::Imports
302                            }
303                            codeprism_lang_js::EdgeKind::Emits => {
304                                codeprism_core::ast::EdgeKind::Emits
305                            }
306                            codeprism_lang_js::EdgeKind::RoutesTo => {
307                                codeprism_core::ast::EdgeKind::RoutesTo
308                            }
309                            codeprism_lang_js::EdgeKind::Raises => {
310                                codeprism_core::ast::EdgeKind::Raises
311                            }
312                            codeprism_lang_js::EdgeKind::Extends => {
313                                codeprism_core::ast::EdgeKind::Extends
314                            }
315                            codeprism_lang_js::EdgeKind::Implements => {
316                                codeprism_core::ast::EdgeKind::Implements
317                            }
318                        };
319
320                        // Convert NodeIds by using hex representation
321                        let codecodeprism_source =
322                            codeprism_core::ast::NodeId::from_hex(&js_edge.source.to_hex())
323                                .unwrap();
324                        let codeprism_target =
325                            codeprism_core::ast::NodeId::from_hex(&js_edge.target.to_hex())
326                                .unwrap();
327
328                        Edge::new(codecodeprism_source, codeprism_target, codeprism_edge_kind)
329                    })
330                    .collect();
331
332                Ok(ParseResult { tree, nodes, edges })
333            }
334            Err(e) => Err(codeprism_core::error::Error::parse(
335                &context.file_path,
336                format!("JavaScript parsing failed: {}", e),
337            )),
338        }
339    }
340}
341
342/// MCP Server implementation that integrates with CodePrism Phase 2.5 components
343pub struct CodePrismMcpServer {
344    /// Repository manager from Phase 2.5
345    repository_manager: RepositoryManager,
346    /// Repository scanner for file discovery
347    scanner: RepositoryScanner,
348    /// Bulk indexer for processing files
349    indexer: BulkIndexer,
350    /// Parser engine for language processing
351    parser_engine: std::sync::Arc<ParserEngine>,
352    /// Graph store for code intelligence
353    graph_store: Arc<GraphStore>,
354    /// Graph query engine
355    graph_query: GraphQuery,
356    /// Content search manager for full-text search
357    content_search: Arc<codeprism_core::ContentSearchManager>,
358    /// Server capabilities
359    capabilities: ServerCapabilities,
360    /// Current repository path
361    repository_path: Option<std::path::PathBuf>,
362}
363
364impl CodePrismMcpServer {
365    /// Create a new MCP server instance
366    pub fn new() -> Result<Self> {
367        let language_registry =
368            std::sync::Arc::new(codeprism_core::parser::LanguageRegistry::new());
369
370        // Register language parsers
371        language_registry.register(Arc::new(PythonParserAdapter));
372        language_registry.register(Arc::new(JavaScriptParserAdapter));
373
374        let parser_engine = std::sync::Arc::new(ParserEngine::new(language_registry.clone()));
375        let repository_manager = RepositoryManager::new(language_registry);
376        let scanner = RepositoryScanner::new();
377        let indexer = BulkIndexer::new(
378            codeprism_core::indexer::IndexingConfig::new("mcp".to_string(), "default".to_string()),
379            parser_engine.clone(),
380        );
381
382        let graph_store = Arc::new(GraphStore::new());
383        let graph_query = GraphQuery::new(graph_store.clone());
384        let content_search = Arc::new(codeprism_core::ContentSearchManager::with_graph_store(
385            graph_store.clone(),
386        ));
387
388        let capabilities = ServerCapabilities {
389            resources: Some(resources::ResourceCapabilities {
390                subscribe: Some(true),
391                list_changed: Some(true),
392            }),
393            tools: Some(tools::ToolCapabilities {
394                list_changed: Some(true),
395            }),
396            prompts: Some(prompts::PromptCapabilities {
397                list_changed: Some(false),
398            }),
399            experimental: Some(HashMap::new()),
400        };
401
402        Ok(Self {
403            repository_manager,
404            scanner,
405            indexer,
406            parser_engine,
407            graph_store,
408            graph_query,
409            content_search,
410            capabilities,
411            repository_path: None,
412        })
413    }
414
415    /// Create a new MCP server instance with custom configuration
416    pub fn new_with_config(
417        memory_limit_mb: usize,
418        batch_size: usize,
419        max_file_size_mb: usize,
420        disable_memory_limit: bool,
421        exclude_dirs: Vec<String>,
422        include_extensions: Option<Vec<String>>,
423        dependency_mode: Option<String>,
424    ) -> Result<Self> {
425        let language_registry =
426            std::sync::Arc::new(codeprism_core::parser::LanguageRegistry::new());
427
428        // Register language parsers
429        language_registry.register(Arc::new(PythonParserAdapter));
430        language_registry.register(Arc::new(JavaScriptParserAdapter));
431
432        let parser_engine = std::sync::Arc::new(ParserEngine::new(language_registry.clone()));
433
434        // Parse dependency mode
435        let dep_mode = match dependency_mode.as_deref() {
436            Some("include_all") => codeprism_core::scanner::DependencyMode::IncludeAll,
437            Some("smart") => codeprism_core::scanner::DependencyMode::Smart,
438            _ => codeprism_core::scanner::DependencyMode::Exclude,
439        };
440
441        // Create repository manager with custom configuration
442        let repository_manager = RepositoryManager::new_with_config(
443            language_registry,
444            Some(exclude_dirs.clone()),
445            include_extensions.clone(),
446            Some(dep_mode.clone()),
447        );
448
449        // Create scanner with custom configuration for direct use
450        let scanner = if !exclude_dirs.is_empty() {
451            let mut scanner = RepositoryScanner::with_exclude_dirs(exclude_dirs.clone())
452                .with_dependency_mode(dep_mode.clone());
453            if let Some(ref extensions) = include_extensions {
454                scanner = scanner.with_extensions(extensions.clone());
455            }
456            scanner
457        } else {
458            RepositoryScanner::new().with_dependency_mode(dep_mode.clone())
459        };
460
461        // Create custom indexing config with user settings
462        let mut indexing_config =
463            codeprism_core::indexer::IndexingConfig::new("mcp".to_string(), "default".to_string());
464        indexing_config.batch_size = batch_size;
465
466        if disable_memory_limit {
467            indexing_config.memory_limit = None;
468            tracing::warn!(
469                "Memory limit checking disabled - use with caution for large repositories"
470            );
471        } else {
472            indexing_config.memory_limit = Some(memory_limit_mb * 1024 * 1024); // Convert MB to bytes
473        }
474
475        let indexer = BulkIndexer::new(indexing_config, parser_engine.clone());
476
477        let graph_store = Arc::new(GraphStore::new());
478        let graph_query = GraphQuery::new(graph_store.clone());
479        let content_search = Arc::new(codeprism_core::ContentSearchManager::with_graph_store(
480            graph_store.clone(),
481        ));
482
483        let capabilities = ServerCapabilities {
484            resources: Some(resources::ResourceCapabilities {
485                subscribe: Some(true),
486                list_changed: Some(true),
487            }),
488            tools: Some(tools::ToolCapabilities {
489                list_changed: Some(true),
490            }),
491            prompts: Some(prompts::PromptCapabilities {
492                list_changed: Some(false),
493            }),
494            experimental: Some(HashMap::new()),
495        };
496
497        tracing::info!("MCP server configured with:");
498        tracing::info!(
499            "  Memory limit: {}MB{}",
500            memory_limit_mb,
501            if disable_memory_limit {
502                " (disabled)"
503            } else {
504                ""
505            }
506        );
507        tracing::info!("  Batch size: {}", batch_size);
508        tracing::info!("  Max file size: {}MB", max_file_size_mb);
509        tracing::info!("  Dependency mode: {:?}", dep_mode);
510        tracing::info!("  Exclude directories: {:?}", exclude_dirs);
511        if let Some(ref exts) = include_extensions {
512            tracing::info!("  Include extensions: {:?}", exts);
513        }
514
515        Ok(Self {
516            repository_manager,
517            scanner,
518            indexer,
519            parser_engine,
520            graph_store,
521            graph_query,
522            content_search,
523            capabilities,
524            repository_path: None,
525        })
526    }
527
528    /// Initialize the server with a repository path
529    pub async fn initialize_with_repository<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
530        let path = path.as_ref().to_path_buf();
531
532        // Create repository config
533        let repo_id = format!(
534            "mcp-{}",
535            path.file_name()
536                .and_then(|n| n.to_str())
537                .unwrap_or("repository")
538        );
539
540        let repo_config = codeprism_core::repository::RepositoryConfig::new(repo_id.clone(), &path)
541            .with_name(
542                path.file_name()
543                    .and_then(|n| n.to_str())
544                    .unwrap_or("repository")
545                    .to_string(),
546            );
547
548        // Register repository
549        self.repository_manager.register_repository(repo_config)?;
550
551        // Perform initial scan and indexing for code symbols
552        let indexing_result = self
553            .repository_manager
554            .index_repository(&repo_id, None)
555            .await?;
556
557        // Populate graph store with indexed data
558        for patch in &indexing_result.patches {
559            for node in &patch.nodes_add {
560                self.graph_store.add_node(node.clone());
561            }
562            for edge in &patch.edges_add {
563                self.graph_store.add_edge(edge.clone());
564            }
565        }
566
567        // Index content for documentation, configuration files, and comments
568        self.index_repository_content(&path).await?;
569
570        self.repository_path = Some(path);
571        tracing::info!(
572            "MCP server initialized with repository: {:?}",
573            self.repository_path
574        );
575
576        Ok(())
577    }
578
579    /// Index repository content including documentation, configuration, and comments
580    async fn index_repository_content(&self, repo_path: &Path) -> Result<()> {
581        tracing::info!("Starting content indexing for repository: {:?}", repo_path);
582
583        // Discover all files in the repository
584        let files = self.scanner.discover_files(repo_path)?;
585        let mut indexed_count = 0;
586        let mut error_count = 0;
587
588        for file_path in files {
589            if let Err(e) = self.index_file_content(&file_path).await {
590                tracing::warn!("Failed to index content for {}: {}", file_path.display(), e);
591                error_count += 1;
592            } else {
593                indexed_count += 1;
594            }
595        }
596
597        tracing::info!(
598            "Content indexing completed: {} files indexed, {} errors",
599            indexed_count,
600            error_count
601        );
602        Ok(())
603    }
604
605    /// Index content for a single file
606    async fn index_file_content(&self, file_path: &Path) -> Result<()> {
607        // Read file content
608        let content = match std::fs::read_to_string(file_path) {
609            Ok(content) => content,
610            Err(_) => {
611                // Skip binary files or files that can't be read as text
612                return Ok(());
613            }
614        };
615
616        // Skip empty files
617        if content.trim().is_empty() {
618            return Ok(());
619        }
620
621        let _language = self.detect_language(file_path);
622
623        // Handle different file types appropriately
624        // For now, use simple file indexing for all content types
625        // TODO: In the future, we can enhance this with tree-sitter integration
626        // to extract comments and provide better source code content indexing
627        self.content_search.index_file(file_path, &content)?;
628
629        Ok(())
630    }
631
632    /// Detect programming language from file extension
633    fn detect_language(&self, file_path: &Path) -> Option<codeprism_core::ast::Language> {
634        let extension = file_path.extension()?.to_str()?;
635        let lang = codeprism_core::ast::Language::from_extension(extension);
636        if matches!(lang, codeprism_core::ast::Language::Unknown) {
637            None
638        } else {
639            Some(lang)
640        }
641    }
642
643    /// Get server capabilities
644    pub fn capabilities(&self) -> &ServerCapabilities {
645        &self.capabilities
646    }
647
648    /// Get repository manager for accessing Phase 2.5 functionality
649    pub fn repository_manager(&self) -> &RepositoryManager {
650        &self.repository_manager
651    }
652
653    /// Get repository scanner
654    pub fn scanner(&self) -> &RepositoryScanner {
655        &self.scanner
656    }
657
658    /// Get bulk indexer
659    pub fn indexer(&self) -> &BulkIndexer {
660        &self.indexer
661    }
662
663    /// Get parser engine
664    pub fn parser_engine(&self) -> &std::sync::Arc<ParserEngine> {
665        &self.parser_engine
666    }
667
668    /// Get graph store
669    pub fn graph_store(&self) -> &Arc<GraphStore> {
670        &self.graph_store
671    }
672
673    /// Get graph query engine
674    pub fn graph_query(&self) -> &GraphQuery {
675        &self.graph_query
676    }
677
678    /// Get content search manager
679    pub fn content_search(&self) -> &Arc<codeprism_core::ContentSearchManager> {
680        &self.content_search
681    }
682
683    /// Get current repository path
684    pub fn repository_path(&self) -> Option<&Path> {
685        self.repository_path.as_deref()
686    }
687}
688
689impl Default for CodePrismMcpServer {
690    fn default() -> Self {
691        Self::new().expect("Failed to create default MCP server")
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use std::fs;
699    use tempfile::TempDir;
700
701    #[tokio::test]
702    async fn test_mcp_server_creation() {
703        let server = CodePrismMcpServer::new().expect("Failed to create MCP server");
704
705        // Verify capabilities are properly set
706        assert!(server.capabilities().resources.is_some());
707        assert!(server.capabilities().tools.is_some());
708        assert!(server.capabilities().prompts.is_some());
709
710        // Verify no repository is set initially
711        assert!(server.repository_path().is_none());
712    }
713
714    #[tokio::test]
715    async fn test_mcp_server_initialize_with_repository() {
716        let temp_dir = TempDir::new().expect("Failed to create temp dir");
717        let repo_path = temp_dir.path();
718
719        // Create a test file
720        fs::write(repo_path.join("test.py"), "print('hello world')").unwrap();
721
722        let mut server = CodePrismMcpServer::new().expect("Failed to create MCP server");
723        server
724            .initialize_with_repository(repo_path)
725            .await
726            .expect("Failed to initialize with repository");
727
728        // Verify repository is set
729        assert!(server.repository_path().is_some());
730        assert_eq!(server.repository_path().unwrap(), repo_path);
731    }
732
733    #[tokio::test]
734    async fn test_mcp_server_capabilities() {
735        let server = CodePrismMcpServer::new().expect("Failed to create MCP server");
736        let capabilities = server.capabilities();
737
738        // Verify resource capabilities
739        let resource_caps = capabilities.resources.as_ref().unwrap();
740        assert_eq!(resource_caps.subscribe, Some(true));
741        assert_eq!(resource_caps.list_changed, Some(true));
742
743        // Verify tool capabilities
744        let tool_caps = capabilities.tools.as_ref().unwrap();
745        assert_eq!(tool_caps.list_changed, Some(true));
746
747        // Verify prompt capabilities
748        let prompt_caps = capabilities.prompts.as_ref().unwrap();
749        assert_eq!(prompt_caps.list_changed, Some(false));
750    }
751}