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 context;
25pub mod error_handler;
26pub mod prompts;
27pub mod protocol;
28pub mod resources;
29pub mod server;
30pub mod tools;
31pub mod tools_legacy;
32pub mod transport;
33
34pub use error_handler::{McpError, McpErrorHandler, McpResult};
36pub use protocol::{
37 InitializeParams, InitializeResult, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse,
38 ServerCapabilities,
39};
40pub use server::McpServer;
41pub use transport::{StdioTransport, Transport};
42
43struct PythonParserAdapter;
45
46impl LanguageParser for PythonParserAdapter {
47 fn language(&self) -> Language {
48 Language::Python
49 }
50
51 fn parse(&self, context: &ParseContext) -> codeprism_core::error::Result<ParseResult> {
52 let python_parser = codeprism_lang_python::PythonLanguageParser::new();
54
55 match codeprism_lang_python::parse_file(
56 &python_parser,
57 &context.repo_id,
58 context.file_path.clone(),
59 context.content.clone(),
60 context.old_tree.clone(),
61 ) {
62 Ok((tree, py_nodes, py_edges)) => {
63 let nodes: Vec<Node> = py_nodes
65 .into_iter()
66 .map(|py_node| {
67 let codeprism_kind = match py_node.kind {
69 codeprism_lang_python::NodeKind::Function => {
70 codeprism_core::ast::NodeKind::Function
71 }
72 codeprism_lang_python::NodeKind::Class => {
73 codeprism_core::ast::NodeKind::Class
74 }
75 codeprism_lang_python::NodeKind::Variable => {
76 codeprism_core::ast::NodeKind::Variable
77 }
78 codeprism_lang_python::NodeKind::Module => {
79 codeprism_core::ast::NodeKind::Module
80 }
81 codeprism_lang_python::NodeKind::Import => {
82 codeprism_core::ast::NodeKind::Import
83 }
84 codeprism_lang_python::NodeKind::Parameter => {
85 codeprism_core::ast::NodeKind::Parameter
86 }
87 codeprism_lang_python::NodeKind::Method => {
88 codeprism_core::ast::NodeKind::Method
89 }
90 codeprism_lang_python::NodeKind::Call => {
91 codeprism_core::ast::NodeKind::Call
92 }
93 codeprism_lang_python::NodeKind::Literal => {
94 codeprism_core::ast::NodeKind::Literal
95 }
96 codeprism_lang_python::NodeKind::Route => {
97 codeprism_core::ast::NodeKind::Route
98 }
99 codeprism_lang_python::NodeKind::SqlQuery => {
100 codeprism_core::ast::NodeKind::SqlQuery
101 }
102 codeprism_lang_python::NodeKind::Event => {
103 codeprism_core::ast::NodeKind::Event
104 }
105 codeprism_lang_python::NodeKind::Unknown => {
106 codeprism_core::ast::NodeKind::Unknown
107 }
108 };
109
110 let codeprism_span = codeprism_core::ast::Span::new(
112 py_node.span.start_byte,
113 py_node.span.end_byte,
114 py_node.span.start_line,
115 py_node.span.end_line,
116 py_node.span.start_column,
117 py_node.span.end_column,
118 );
119
120 Node::new(
121 &context.repo_id,
122 codeprism_kind,
123 py_node.name,
124 Language::Python,
125 context.file_path.clone(),
126 codeprism_span,
127 )
128 })
129 .collect();
130
131 let edges: Vec<Edge> = py_edges
132 .into_iter()
133 .map(|py_edge| {
134 let codeprism_edge_kind = match py_edge.kind {
136 codeprism_lang_python::EdgeKind::Calls => {
137 codeprism_core::ast::EdgeKind::Calls
138 }
139 codeprism_lang_python::EdgeKind::Reads => {
140 codeprism_core::ast::EdgeKind::Reads
141 }
142 codeprism_lang_python::EdgeKind::Writes => {
143 codeprism_core::ast::EdgeKind::Writes
144 }
145 codeprism_lang_python::EdgeKind::Imports => {
146 codeprism_core::ast::EdgeKind::Imports
147 }
148 codeprism_lang_python::EdgeKind::Emits => {
149 codeprism_core::ast::EdgeKind::Emits
150 }
151 codeprism_lang_python::EdgeKind::RoutesTo => {
152 codeprism_core::ast::EdgeKind::RoutesTo
153 }
154 codeprism_lang_python::EdgeKind::Raises => {
155 codeprism_core::ast::EdgeKind::Raises
156 }
157 codeprism_lang_python::EdgeKind::Extends => {
158 codeprism_core::ast::EdgeKind::Extends
159 }
160 codeprism_lang_python::EdgeKind::Implements => {
161 codeprism_core::ast::EdgeKind::Implements
162 }
163 };
164
165 let codecodeprism_source =
167 codeprism_core::ast::NodeId::from_hex(&py_edge.source.to_hex())
168 .unwrap();
169 let codeprism_target =
170 codeprism_core::ast::NodeId::from_hex(&py_edge.target.to_hex())
171 .unwrap();
172
173 Edge::new(codecodeprism_source, codeprism_target, codeprism_edge_kind)
174 })
175 .collect();
176
177 Ok(ParseResult { tree, nodes, edges })
178 }
179 Err(e) => Err(codeprism_core::error::Error::parse(
180 &context.file_path,
181 format!("Python parsing failed: {}", e),
182 )),
183 }
184 }
185}
186
187struct JavaScriptParserAdapter;
189
190impl LanguageParser for JavaScriptParserAdapter {
191 fn language(&self) -> Language {
192 Language::JavaScript
193 }
194
195 fn parse(&self, context: &ParseContext) -> codeprism_core::error::Result<ParseResult> {
196 let js_parser = codeprism_lang_js::JavaScriptLanguageParser::new();
198
199 match codeprism_lang_js::parse_file(
200 &js_parser,
201 &context.repo_id,
202 context.file_path.clone(),
203 context.content.clone(),
204 context.old_tree.clone(),
205 ) {
206 Ok((tree, js_nodes, js_edges)) => {
207 let nodes: Vec<Node> = js_nodes
209 .into_iter()
210 .map(|js_node| {
211 let codeprism_kind = match js_node.kind {
213 codeprism_lang_js::NodeKind::Function => {
214 codeprism_core::ast::NodeKind::Function
215 }
216 codeprism_lang_js::NodeKind::Class => {
217 codeprism_core::ast::NodeKind::Class
218 }
219 codeprism_lang_js::NodeKind::Variable => {
220 codeprism_core::ast::NodeKind::Variable
221 }
222 codeprism_lang_js::NodeKind::Module => {
223 codeprism_core::ast::NodeKind::Module
224 }
225 codeprism_lang_js::NodeKind::Import => {
226 codeprism_core::ast::NodeKind::Import
227 }
228 codeprism_lang_js::NodeKind::Parameter => {
229 codeprism_core::ast::NodeKind::Parameter
230 }
231 codeprism_lang_js::NodeKind::Method => {
232 codeprism_core::ast::NodeKind::Method
233 }
234 codeprism_lang_js::NodeKind::Call => {
235 codeprism_core::ast::NodeKind::Call
236 }
237 codeprism_lang_js::NodeKind::Literal => {
238 codeprism_core::ast::NodeKind::Literal
239 }
240 codeprism_lang_js::NodeKind::Route => {
241 codeprism_core::ast::NodeKind::Route
242 }
243 codeprism_lang_js::NodeKind::SqlQuery => {
244 codeprism_core::ast::NodeKind::SqlQuery
245 }
246 codeprism_lang_js::NodeKind::Event => {
247 codeprism_core::ast::NodeKind::Event
248 }
249 codeprism_lang_js::NodeKind::Unknown => {
250 codeprism_core::ast::NodeKind::Unknown
251 }
252 };
253
254 let codeprism_span = codeprism_core::ast::Span::new(
256 js_node.span.start_byte,
257 js_node.span.end_byte,
258 js_node.span.start_line,
259 js_node.span.end_line,
260 js_node.span.start_column,
261 js_node.span.end_column,
262 );
263
264 Node::new(
265 &context.repo_id,
266 codeprism_kind,
267 js_node.name,
268 Language::JavaScript,
269 context.file_path.clone(),
270 codeprism_span,
271 )
272 })
273 .collect();
274
275 let edges: Vec<Edge> = js_edges
276 .into_iter()
277 .map(|js_edge| {
278 let codeprism_edge_kind = match js_edge.kind {
280 codeprism_lang_js::EdgeKind::Calls => {
281 codeprism_core::ast::EdgeKind::Calls
282 }
283 codeprism_lang_js::EdgeKind::Reads => {
284 codeprism_core::ast::EdgeKind::Reads
285 }
286 codeprism_lang_js::EdgeKind::Writes => {
287 codeprism_core::ast::EdgeKind::Writes
288 }
289 codeprism_lang_js::EdgeKind::Imports => {
290 codeprism_core::ast::EdgeKind::Imports
291 }
292 codeprism_lang_js::EdgeKind::Emits => {
293 codeprism_core::ast::EdgeKind::Emits
294 }
295 codeprism_lang_js::EdgeKind::RoutesTo => {
296 codeprism_core::ast::EdgeKind::RoutesTo
297 }
298 codeprism_lang_js::EdgeKind::Raises => {
299 codeprism_core::ast::EdgeKind::Raises
300 }
301 codeprism_lang_js::EdgeKind::Extends => {
302 codeprism_core::ast::EdgeKind::Extends
303 }
304 codeprism_lang_js::EdgeKind::Implements => {
305 codeprism_core::ast::EdgeKind::Implements
306 }
307 };
308
309 let codecodeprism_source =
311 codeprism_core::ast::NodeId::from_hex(&js_edge.source.to_hex())
312 .unwrap();
313 let codeprism_target =
314 codeprism_core::ast::NodeId::from_hex(&js_edge.target.to_hex())
315 .unwrap();
316
317 Edge::new(codecodeprism_source, codeprism_target, codeprism_edge_kind)
318 })
319 .collect();
320
321 Ok(ParseResult { tree, nodes, edges })
322 }
323 Err(e) => Err(codeprism_core::error::Error::parse(
324 &context.file_path,
325 format!("JavaScript parsing failed: {}", e),
326 )),
327 }
328 }
329}
330
331pub struct CodePrismMcpServer {
333 repository_manager: RepositoryManager,
335 scanner: RepositoryScanner,
337 indexer: BulkIndexer,
339 parser_engine: std::sync::Arc<ParserEngine>,
341 graph_store: Arc<GraphStore>,
343 graph_query: GraphQuery,
345 content_search: Arc<codeprism_core::ContentSearchManager>,
347 capabilities: ServerCapabilities,
349 repository_path: Option<std::path::PathBuf>,
351}
352
353impl CodePrismMcpServer {
354 pub fn new() -> Result<Self> {
356 let language_registry =
357 std::sync::Arc::new(codeprism_core::parser::LanguageRegistry::new());
358
359 language_registry.register(Arc::new(PythonParserAdapter));
361 language_registry.register(Arc::new(JavaScriptParserAdapter));
362
363 let parser_engine = std::sync::Arc::new(ParserEngine::new(language_registry.clone()));
364 let repository_manager = RepositoryManager::new(language_registry);
365 let scanner = RepositoryScanner::new();
366 let indexer = BulkIndexer::new(
367 codeprism_core::indexer::IndexingConfig::new("mcp".to_string(), "default".to_string()),
368 parser_engine.clone(),
369 );
370
371 let graph_store = Arc::new(GraphStore::new());
372 let graph_query = GraphQuery::new(graph_store.clone());
373 let content_search = Arc::new(codeprism_core::ContentSearchManager::with_graph_store(
374 graph_store.clone(),
375 ));
376
377 let capabilities = ServerCapabilities {
378 resources: Some(resources::ResourceCapabilities {
379 subscribe: Some(true),
380 list_changed: Some(true),
381 }),
382 tools: Some(tools::ToolCapabilities {
383 list_changed: Some(true),
384 }),
385 prompts: Some(prompts::PromptCapabilities {
386 list_changed: Some(false),
387 }),
388 experimental: Some(HashMap::new()),
389 };
390
391 Ok(Self {
392 repository_manager,
393 scanner,
394 indexer,
395 parser_engine,
396 graph_store,
397 graph_query,
398 content_search,
399 capabilities,
400 repository_path: None,
401 })
402 }
403
404 pub fn new_with_config(
406 memory_limit_mb: usize,
407 batch_size: usize,
408 max_file_size_mb: usize,
409 disable_memory_limit: bool,
410 exclude_dirs: Vec<String>,
411 include_extensions: Option<Vec<String>>,
412 dependency_mode: Option<String>,
413 ) -> Result<Self> {
414 let language_registry =
415 std::sync::Arc::new(codeprism_core::parser::LanguageRegistry::new());
416
417 language_registry.register(Arc::new(PythonParserAdapter));
419 language_registry.register(Arc::new(JavaScriptParserAdapter));
420
421 let parser_engine = std::sync::Arc::new(ParserEngine::new(language_registry.clone()));
422
423 let dep_mode = match dependency_mode.as_deref() {
425 Some("include_all") => codeprism_core::scanner::DependencyMode::IncludeAll,
426 Some("smart") => codeprism_core::scanner::DependencyMode::Smart,
427 _ => codeprism_core::scanner::DependencyMode::Exclude,
428 };
429
430 let repository_manager = RepositoryManager::new_with_config(
432 language_registry,
433 Some(exclude_dirs.clone()),
434 include_extensions.clone(),
435 Some(dep_mode.clone()),
436 );
437
438 let scanner = if !exclude_dirs.is_empty() {
440 let mut scanner = RepositoryScanner::with_exclude_dirs(exclude_dirs.clone())
441 .with_dependency_mode(dep_mode.clone());
442 if let Some(ref extensions) = include_extensions {
443 scanner = scanner.with_extensions(extensions.clone());
444 }
445 scanner
446 } else {
447 RepositoryScanner::new().with_dependency_mode(dep_mode.clone())
448 };
449
450 let mut indexing_config =
452 codeprism_core::indexer::IndexingConfig::new("mcp".to_string(), "default".to_string());
453 indexing_config.batch_size = batch_size;
454
455 if disable_memory_limit {
456 indexing_config.memory_limit = None;
457 tracing::warn!(
458 "Memory limit checking disabled - use with caution for large repositories"
459 );
460 } else {
461 indexing_config.memory_limit = Some(memory_limit_mb * 1024 * 1024); }
463
464 let indexer = BulkIndexer::new(indexing_config, parser_engine.clone());
465
466 let graph_store = Arc::new(GraphStore::new());
467 let graph_query = GraphQuery::new(graph_store.clone());
468 let content_search = Arc::new(codeprism_core::ContentSearchManager::with_graph_store(
469 graph_store.clone(),
470 ));
471
472 let capabilities = ServerCapabilities {
473 resources: Some(resources::ResourceCapabilities {
474 subscribe: Some(true),
475 list_changed: Some(true),
476 }),
477 tools: Some(tools::ToolCapabilities {
478 list_changed: Some(true),
479 }),
480 prompts: Some(prompts::PromptCapabilities {
481 list_changed: Some(false),
482 }),
483 experimental: Some(HashMap::new()),
484 };
485
486 tracing::info!("MCP server configured with:");
487 tracing::info!(
488 " Memory limit: {}MB{}",
489 memory_limit_mb,
490 if disable_memory_limit {
491 " (disabled)"
492 } else {
493 ""
494 }
495 );
496 tracing::info!(" Batch size: {}", batch_size);
497 tracing::info!(" Max file size: {}MB", max_file_size_mb);
498 tracing::info!(" Dependency mode: {:?}", dep_mode);
499 tracing::info!(" Exclude directories: {:?}", exclude_dirs);
500 if let Some(ref exts) = include_extensions {
501 tracing::info!(" Include extensions: {:?}", exts);
502 }
503
504 Ok(Self {
505 repository_manager,
506 scanner,
507 indexer,
508 parser_engine,
509 graph_store,
510 graph_query,
511 content_search,
512 capabilities,
513 repository_path: None,
514 })
515 }
516
517 pub async fn initialize_with_repository<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
519 let path = path.as_ref().to_path_buf();
520
521 let repo_id = format!(
523 "mcp-{}",
524 path.file_name()
525 .and_then(|n| n.to_str())
526 .unwrap_or("repository")
527 );
528
529 let repo_config = codeprism_core::repository::RepositoryConfig::new(repo_id.clone(), &path)
530 .with_name(
531 path.file_name()
532 .and_then(|n| n.to_str())
533 .unwrap_or("repository")
534 .to_string(),
535 );
536
537 self.repository_manager.register_repository(repo_config)?;
539
540 let indexing_result = self
542 .repository_manager
543 .index_repository(&repo_id, None)
544 .await?;
545
546 for patch in &indexing_result.patches {
548 for node in &patch.nodes_add {
549 self.graph_store.add_node(node.clone());
550 }
551 for edge in &patch.edges_add {
552 self.graph_store.add_edge(edge.clone());
553 }
554 }
555
556 self.index_repository_content(&path).await?;
558
559 self.repository_path = Some(path);
560 tracing::info!(
561 "MCP server initialized with repository: {:?}",
562 self.repository_path
563 );
564
565 Ok(())
566 }
567
568 async fn index_repository_content(&self, repo_path: &Path) -> Result<()> {
570 tracing::info!("Starting content indexing for repository: {:?}", repo_path);
571
572 let files = self.scanner.discover_files(repo_path)?;
574 let mut indexed_count = 0;
575 let mut error_count = 0;
576
577 for file_path in files {
578 if let Err(e) = self.index_file_content(&file_path).await {
579 tracing::warn!("Failed to index content for {}: {}", file_path.display(), e);
580 error_count += 1;
581 } else {
582 indexed_count += 1;
583 }
584 }
585
586 tracing::info!(
587 "Content indexing completed: {} files indexed, {} errors",
588 indexed_count,
589 error_count
590 );
591 Ok(())
592 }
593
594 async fn index_file_content(&self, file_path: &Path) -> Result<()> {
596 let content = match std::fs::read_to_string(file_path) {
598 Ok(content) => content,
599 Err(_) => {
600 return Ok(());
602 }
603 };
604
605 if content.trim().is_empty() {
607 return Ok(());
608 }
609
610 let _language = self.detect_language(file_path);
611
612 self.content_search.index_file(file_path, &content)?;
617
618 Ok(())
619 }
620
621 fn detect_language(&self, file_path: &Path) -> Option<codeprism_core::ast::Language> {
623 let extension = file_path.extension()?.to_str()?;
624 let lang = codeprism_core::ast::Language::from_extension(extension);
625 if matches!(lang, codeprism_core::ast::Language::Unknown) {
626 None
627 } else {
628 Some(lang)
629 }
630 }
631
632 pub fn capabilities(&self) -> &ServerCapabilities {
634 &self.capabilities
635 }
636
637 pub fn repository_manager(&self) -> &RepositoryManager {
639 &self.repository_manager
640 }
641
642 pub fn scanner(&self) -> &RepositoryScanner {
644 &self.scanner
645 }
646
647 pub fn indexer(&self) -> &BulkIndexer {
649 &self.indexer
650 }
651
652 pub fn parser_engine(&self) -> &std::sync::Arc<ParserEngine> {
654 &self.parser_engine
655 }
656
657 pub fn graph_store(&self) -> &Arc<GraphStore> {
659 &self.graph_store
660 }
661
662 pub fn graph_query(&self) -> &GraphQuery {
664 &self.graph_query
665 }
666
667 pub fn content_search(&self) -> &Arc<codeprism_core::ContentSearchManager> {
669 &self.content_search
670 }
671
672 pub fn repository_path(&self) -> Option<&Path> {
674 self.repository_path.as_deref()
675 }
676}
677
678impl Default for CodePrismMcpServer {
679 fn default() -> Self {
680 Self::new().expect("Failed to create default MCP server")
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687 use std::fs;
688 use tempfile::TempDir;
689
690 #[tokio::test]
691 async fn test_mcp_server_creation() {
692 let server = CodePrismMcpServer::new().expect("Failed to create MCP server");
693
694 assert!(server.capabilities().resources.is_some());
696 assert!(server.capabilities().tools.is_some());
697 assert!(server.capabilities().prompts.is_some());
698
699 assert!(server.repository_path().is_none());
701 }
702
703 #[tokio::test]
704 async fn test_mcp_server_initialize_with_repository() {
705 let temp_dir = TempDir::new().expect("Failed to create temp dir");
706 let repo_path = temp_dir.path();
707
708 fs::write(repo_path.join("test.py"), "print('hello world')").unwrap();
710
711 let mut server = CodePrismMcpServer::new().expect("Failed to create MCP server");
712 server
713 .initialize_with_repository(repo_path)
714 .await
715 .expect("Failed to initialize with repository");
716
717 assert!(server.repository_path().is_some());
719 assert_eq!(server.repository_path().unwrap(), repo_path);
720 }
721
722 #[tokio::test]
723 async fn test_mcp_server_capabilities() {
724 let server = CodePrismMcpServer::new().expect("Failed to create MCP server");
725 let capabilities = server.capabilities();
726
727 let resource_caps = capabilities.resources.as_ref().unwrap();
729 assert_eq!(resource_caps.subscribe, Some(true));
730 assert_eq!(resource_caps.list_changed, Some(true));
731
732 let tool_caps = capabilities.tools.as_ref().unwrap();
734 assert_eq!(tool_caps.list_changed, Some(true));
735
736 let prompt_caps = capabilities.prompts.as_ref().unwrap();
738 assert_eq!(prompt_caps.list_changed, Some(false));
739 }
740}