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