1use 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; pub mod context;
26pub mod error_handler;
27pub mod monitoring; pub mod prompts;
29pub mod protocol;
30pub mod resources;
31pub mod server;
32pub mod tools;
33pub mod tools_legacy;
34pub mod transport;
35pub mod validation; pub 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
46pub 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
54struct 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 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 let nodes: Vec<Node> = py_nodes
76 .into_iter()
77 .map(|py_node| {
78 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 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 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 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
198struct 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 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 let nodes: Vec<Node> = js_nodes
220 .into_iter()
221 .map(|js_node| {
222 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 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 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 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
342pub struct CodePrismMcpServer {
344 repository_manager: RepositoryManager,
346 scanner: RepositoryScanner,
348 indexer: BulkIndexer,
350 parser_engine: std::sync::Arc<ParserEngine>,
352 graph_store: Arc<GraphStore>,
354 graph_query: GraphQuery,
356 content_search: Arc<codeprism_core::ContentSearchManager>,
358 capabilities: ServerCapabilities,
360 repository_path: Option<std::path::PathBuf>,
362}
363
364impl CodePrismMcpServer {
365 pub fn new() -> Result<Self> {
367 let language_registry =
368 std::sync::Arc::new(codeprism_core::parser::LanguageRegistry::new());
369
370 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 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 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 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 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 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 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); }
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 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 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 self.repository_manager.register_repository(repo_config)?;
550
551 let indexing_result = self
553 .repository_manager
554 .index_repository(&repo_id, None)
555 .await?;
556
557 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 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 async fn index_repository_content(&self, repo_path: &Path) -> Result<()> {
581 tracing::info!("Starting content indexing for repository: {:?}", repo_path);
582
583 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 async fn index_file_content(&self, file_path: &Path) -> Result<()> {
607 let content = match std::fs::read_to_string(file_path) {
609 Ok(content) => content,
610 Err(_) => {
611 return Ok(());
613 }
614 };
615
616 if content.trim().is_empty() {
618 return Ok(());
619 }
620
621 let _language = self.detect_language(file_path);
622
623 self.content_search.index_file(file_path, &content)?;
628
629 Ok(())
630 }
631
632 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 pub fn capabilities(&self) -> &ServerCapabilities {
645 &self.capabilities
646 }
647
648 pub fn repository_manager(&self) -> &RepositoryManager {
650 &self.repository_manager
651 }
652
653 pub fn scanner(&self) -> &RepositoryScanner {
655 &self.scanner
656 }
657
658 pub fn indexer(&self) -> &BulkIndexer {
660 &self.indexer
661 }
662
663 pub fn parser_engine(&self) -> &std::sync::Arc<ParserEngine> {
665 &self.parser_engine
666 }
667
668 pub fn graph_store(&self) -> &Arc<GraphStore> {
670 &self.graph_store
671 }
672
673 pub fn graph_query(&self) -> &GraphQuery {
675 &self.graph_query
676 }
677
678 pub fn content_search(&self) -> &Arc<codeprism_core::ContentSearchManager> {
680 &self.content_search
681 }
682
683 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 assert!(server.capabilities().resources.is_some());
707 assert!(server.capabilities().tools.is_some());
708 assert!(server.capabilities().prompts.is_some());
709
710 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 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 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 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 let tool_caps = capabilities.tools.as_ref().unwrap();
745 assert_eq!(tool_caps.list_changed, Some(true));
746
747 let prompt_caps = capabilities.prompts.as_ref().unwrap();
749 assert_eq!(prompt_caps.list_changed, Some(false));
750 }
751}