1use crate::context::session::SessionId;
4use crate::context::workflow::ConfidenceLevel;
5use crate::context::ToolSuggestion;
6use crate::tools::{CallToolParams, CallToolResult, Tool, ToolContent};
7use crate::CodePrismMcpServer;
8use anyhow::Result;
9use serde_json::Value;
10
11pub fn list_tools() -> Vec<Tool> {
13 vec![
14 Tool {
15 name: "explain_symbol".to_string(),
16 title: Some("Explain Symbol".to_string()),
17 description: "Provide detailed explanation of a code symbol with context".to_string(),
18 input_schema: serde_json::json!({
19 "type": "object",
20 "properties": {
21 "symbol_id": {
22 "type": "string",
23 "description": "Symbol identifier (node ID)"
24 },
25 "include_dependencies": {
26 "type": "boolean",
27 "description": "Include dependency information",
28 "default": false
29 },
30 "include_usages": {
31 "type": "boolean",
32 "description": "Include usage information",
33 "default": false
34 },
35 "context_lines": {
36 "type": "number",
37 "description": "Number of lines before and after the symbol to include as context",
38 "default": 4
39 }
40 },
41 "required": ["symbol_id"]
42 }),
43 },
44 Tool {
45 name: "search_symbols".to_string(),
46 title: Some("Search Symbols".to_string()),
47 description: "Search for symbols by name pattern with advanced inheritance filtering"
48 .to_string(),
49 input_schema: serde_json::json!({
50 "type": "object",
51 "properties": {
52 "pattern": {
53 "type": "string",
54 "description": "Search pattern (supports regex)"
55 },
56 "symbol_types": {
57 "type": "array",
58 "items": {
59 "type": "string",
60 "enum": ["function", "class", "variable", "module", "method"]
61 },
62 "description": "Filter by symbol types"
63 },
64 "inheritance_filters": {
65 "type": "array",
66 "items": {
67 "type": "string"
68 },
69 "description": "Filter by inheritance relationships (format: 'inherits_from:ClassName', 'metaclass:MetaclassName', 'uses_mixin:MixinName')"
70 },
71 "limit": {
72 "type": "number",
73 "description": "Maximum number of results",
74 "default": 50
75 },
76 "context_lines": {
77 "type": "number",
78 "description": "Number of lines before and after the symbol to include as context",
79 "default": 4
80 }
81 },
82 "required": ["pattern"]
83 }),
84 },
85 ]
86}
87
88pub async fn call_tool(
90 server: &CodePrismMcpServer,
91 params: &CallToolParams,
92) -> Result<CallToolResult> {
93 match params.name.as_str() {
94 "explain_symbol" => explain_symbol(server, params.arguments.as_ref()).await,
95 "search_symbols" => search_symbols(server, params.arguments.as_ref()).await,
96 _ => Err(anyhow::anyhow!("Unknown symbol tool: {}", params.name)),
97 }
98}
99
100fn parse_node_id(hex_str: &str) -> Result<codeprism_core::NodeId> {
102 codeprism_core::NodeId::from_hex(hex_str)
103 .map_err(|e| anyhow::anyhow!("Invalid node ID '{}': {}", hex_str, e))
104}
105
106async fn resolve_symbol_name(
108 server: &CodePrismMcpServer,
109 symbol_name: &str,
110) -> Result<Option<codeprism_core::NodeId>> {
111 let results = server
113 .graph_query()
114 .search_symbols(symbol_name, None, Some(10))?;
115
116 for result in &results {
118 if result.node.name == symbol_name {
119 return Ok(Some(result.node.id));
120 }
121 }
122
123 if let Some(first) = results.first() {
125 Ok(Some(first.node.id))
126 } else {
127 Ok(None)
128 }
129}
130
131async fn resolve_symbol_identifier(
133 server: &CodePrismMcpServer,
134 identifier: &str,
135) -> Result<codeprism_core::NodeId> {
136 if let Ok(node_id) = parse_node_id(identifier) {
138 return Ok(node_id);
139 }
140
141 if let Some(node_id) = resolve_symbol_name(server, identifier).await? {
143 return Ok(node_id);
144 }
145
146 Err(anyhow::anyhow!("Could not resolve symbol identifier '{}'. Please provide either a valid node ID (hex string) or symbol name that exists in the codebase.", identifier))
147}
148
149fn extract_source_context(
151 file_path: &std::path::Path,
152 line_number: usize,
153 context_lines: usize,
154) -> Option<serde_json::Value> {
155 if let Ok(content) = std::fs::read_to_string(file_path) {
156 let lines: Vec<&str> = content.lines().collect();
157 let total_lines = lines.len();
158
159 if line_number == 0 || line_number > total_lines {
160 return None;
161 }
162
163 let target_line_idx = line_number - 1;
165
166 let start_idx = target_line_idx.saturating_sub(context_lines);
168 let end_idx = std::cmp::min(target_line_idx + context_lines + 1, total_lines);
169
170 let context_lines_data: Vec<serde_json::Value> = (start_idx..end_idx)
171 .map(|idx| {
172 serde_json::json!({
173 "line_number": idx + 1,
174 "content": lines[idx],
175 "is_target": idx == target_line_idx
176 })
177 })
178 .collect();
179
180 Some(serde_json::json!({
181 "file": file_path.display().to_string(),
182 "target_line": line_number,
183 "context_start": start_idx + 1,
184 "context_end": end_idx,
185 "lines": context_lines_data
186 }))
187 } else {
188 None
189 }
190}
191
192fn create_node_info_with_context(
194 node: &codeprism_core::Node,
195 context_lines: usize,
196) -> serde_json::Value {
197 let mut info = serde_json::json!({
198 "id": node.id.to_hex(),
199 "name": node.name,
200 "kind": format!("{:?}", node.kind),
201 "file": node.file.display().to_string(),
202 "span": {
203 "start_line": node.span.start_line,
204 "end_line": node.span.end_line,
205 "start_column": node.span.start_column,
206 "end_column": node.span.end_column
207 }
208 });
209
210 if let Some(context) = extract_source_context(&node.file, node.span.start_line, context_lines) {
211 info["source_context"] = context;
212 }
213
214 info
215}
216
217fn is_valid_dependency_node(node: &codeprism_core::Node) -> bool {
219 if matches!(node.kind, codeprism_core::NodeKind::Call) {
221 if node.name.is_empty()
223 || node.name == ")"
224 || node.name == "("
225 || node.name.trim().is_empty()
226 || node.name.chars().all(|c| !c.is_alphanumeric() && c != '_')
227 {
228 return false;
229 }
230 }
231
232 true
234}
235
236fn parse_inheritance_filter(filter_str: &str) -> Option<codeprism_core::InheritanceFilter> {
238 if let Some(colon_pos) = filter_str.find(':') {
239 let filter_type = &filter_str[..colon_pos];
240 let class_name = &filter_str[colon_pos + 1..];
241
242 match filter_type {
243 "inherits_from" => Some(codeprism_core::InheritanceFilter::InheritsFrom(
244 class_name.to_string(),
245 )),
246 "metaclass" => Some(codeprism_core::InheritanceFilter::HasMetaclass(
247 class_name.to_string(),
248 )),
249 "uses_mixin" => Some(codeprism_core::InheritanceFilter::UsesMixin(
250 class_name.to_string(),
251 )),
252 _ => None,
253 }
254 } else {
255 None
256 }
257}
258
259fn generate_symbol_workflow_suggestions(
261 symbol_id_str: &str,
262 node: &codeprism_core::Node,
263) -> Vec<ToolSuggestion> {
264 let mut suggestions = Vec::new();
265
266 suggestions.push(
268 ToolSuggestion::new(
269 "find_references".to_string(),
270 ConfidenceLevel::High,
271 format!("Find all references to symbol '{}'", node.name),
272 "Understanding of how and where the symbol is used throughout the codebase".to_string(),
273 1,
274 )
275 .with_parameter(
276 "symbol_id".to_string(),
277 serde_json::Value::String(symbol_id_str.to_string()),
278 ),
279 );
280
281 suggestions.push(
283 ToolSuggestion::new(
284 "find_dependencies".to_string(),
285 ConfidenceLevel::Medium,
286 format!("Analyze dependencies for symbol '{}'", node.name),
287 "Understanding of what this symbol depends on".to_string(),
288 2,
289 )
290 .with_parameter(
291 "target".to_string(),
292 serde_json::Value::String(symbol_id_str.to_string()),
293 ),
294 );
295
296 if matches!(node.kind, codeprism_core::NodeKind::Class) {
298 suggestions.push(
299 ToolSuggestion::new(
300 "trace_inheritance".to_string(),
301 ConfidenceLevel::Medium,
302 format!("Analyze inheritance hierarchy for class '{}'", node.name),
303 "Complete understanding of inheritance relationships and method resolution"
304 .to_string(),
305 3,
306 )
307 .with_parameter(
308 "class_name".to_string(),
309 serde_json::Value::String(node.name.clone()),
310 ),
311 );
312 }
313
314 if matches!(
316 node.kind,
317 codeprism_core::NodeKind::Function | codeprism_core::NodeKind::Method
318 ) {
319 suggestions.push(
320 ToolSuggestion::new(
321 "trace_data_flow".to_string(),
322 ConfidenceLevel::Medium,
323 format!("Trace data flow through function '{}'", node.name),
324 "Understanding of how data flows through this function".to_string(),
325 3,
326 )
327 .with_parameter(
328 "function_id".to_string(),
329 serde_json::Value::String(symbol_id_str.to_string()),
330 ),
331 );
332 }
333
334 suggestions
335}
336
337async fn explain_symbol(
339 server: &CodePrismMcpServer,
340 arguments: Option<&Value>,
341) -> Result<CallToolResult> {
342 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
343
344 let symbol_id_str = args
346 .get("symbol_id")
347 .or_else(|| args.get("symbol"))
348 .and_then(|v| v.as_str())
349 .ok_or_else(|| anyhow::anyhow!("Missing symbol_id parameter (or symbol)"))?;
350
351 let include_dependencies = args
352 .get("include_dependencies")
353 .and_then(|v| v.as_bool())
354 .unwrap_or(false);
355
356 let include_usages = args
357 .get("include_usages")
358 .and_then(|v| v.as_bool())
359 .unwrap_or(false);
360
361 let context_lines = args
362 .get("context_lines")
363 .and_then(|v| v.as_u64())
364 .map(|v| v as usize)
365 .unwrap_or(4);
366
367 let _session_id = args
369 .get("session_id")
370 .and_then(|v| v.as_str())
371 .map(|s| SessionId(s.to_string()));
372
373 let symbol_id = resolve_symbol_identifier(server, symbol_id_str).await?;
374
375 if let Some(node) = server.graph_store().get_node(&symbol_id) {
376 let mut result = serde_json::json!({
377 "symbol": create_node_info_with_context(&node, context_lines)
378 });
379
380 if matches!(node.kind, codeprism_core::NodeKind::Class) {
382 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol_id) {
383 let mut inheritance_data = serde_json::Map::new();
384
385 inheritance_data.insert(
387 "class_name".to_string(),
388 serde_json::Value::String(inheritance_info.class_name),
389 );
390 inheritance_data.insert(
391 "is_metaclass".to_string(),
392 serde_json::Value::Bool(inheritance_info.is_metaclass),
393 );
394
395 if !inheritance_info.base_classes.is_empty() {
397 let base_classes: Vec<_> = inheritance_info
398 .base_classes
399 .iter()
400 .map(|rel| {
401 serde_json::json!({
402 "name": rel.class_name,
403 "relationship_type": rel.relationship_type,
404 "file": rel.file.display().to_string(),
405 "span": {
406 "start_line": rel.span.start_line,
407 "end_line": rel.span.end_line,
408 "start_column": rel.span.start_column,
409 "end_column": rel.span.end_column
410 }
411 })
412 })
413 .collect();
414 inheritance_data.insert(
415 "base_classes".to_string(),
416 serde_json::Value::Array(base_classes),
417 );
418 }
419
420 if !inheritance_info.subclasses.is_empty() {
422 let subclasses: Vec<_> = inheritance_info
423 .subclasses
424 .iter()
425 .map(|rel| {
426 serde_json::json!({
427 "name": rel.class_name,
428 "file": rel.file.display().to_string(),
429 "span": {
430 "start_line": rel.span.start_line,
431 "end_line": rel.span.end_line,
432 "start_column": rel.span.start_column,
433 "end_column": rel.span.end_column
434 }
435 })
436 })
437 .collect();
438 inheritance_data.insert(
439 "subclasses".to_string(),
440 serde_json::Value::Array(subclasses),
441 );
442 }
443
444 if let Some(metaclass) = inheritance_info.metaclass {
446 inheritance_data.insert(
447 "metaclass".to_string(),
448 serde_json::json!({
449 "name": metaclass.class_name,
450 "file": metaclass.file.display().to_string(),
451 "span": {
452 "start_line": metaclass.span.start_line,
453 "end_line": metaclass.span.end_line,
454 "start_column": metaclass.span.start_column,
455 "end_column": metaclass.span.end_column
456 }
457 }),
458 );
459 }
460
461 if !inheritance_info.mixins.is_empty() {
463 let mixins: Vec<_> = inheritance_info
464 .mixins
465 .iter()
466 .map(|rel| {
467 serde_json::json!({
468 "name": rel.class_name,
469 "file": rel.file.display().to_string(),
470 "span": {
471 "start_line": rel.span.start_line,
472 "end_line": rel.span.end_line,
473 "start_column": rel.span.start_column,
474 "end_column": rel.span.end_column
475 }
476 })
477 })
478 .collect();
479 inheritance_data.insert("mixins".to_string(), serde_json::Value::Array(mixins));
480 }
481
482 if !inheritance_info.method_resolution_order.is_empty() {
484 inheritance_data.insert(
485 "method_resolution_order".to_string(),
486 serde_json::Value::Array(
487 inheritance_info
488 .method_resolution_order
489 .iter()
490 .map(|name| serde_json::Value::String(name.clone()))
491 .collect(),
492 ),
493 );
494 }
495
496 if !inheritance_info.dynamic_attributes.is_empty() {
498 let dynamic_attrs: Vec<_> = inheritance_info
499 .dynamic_attributes
500 .iter()
501 .map(|attr| {
502 serde_json::json!({
503 "name": attr.name,
504 "created_by": attr.created_by,
505 "type": attr.attribute_type
506 })
507 })
508 .collect();
509 inheritance_data.insert(
510 "dynamic_attributes".to_string(),
511 serde_json::Value::Array(dynamic_attrs),
512 );
513 }
514
515 if !inheritance_info.inheritance_chain.is_empty() {
517 inheritance_data.insert(
518 "inheritance_chain".to_string(),
519 serde_json::Value::Array(
520 inheritance_info
521 .inheritance_chain
522 .iter()
523 .map(|name| serde_json::Value::String(name.clone()))
524 .collect(),
525 ),
526 );
527 }
528
529 result["inheritance"] = serde_json::Value::Object(inheritance_data);
530 }
531 }
532
533 if include_dependencies {
534 let dependencies = server
535 .graph_query()
536 .find_dependencies(&symbol_id, codeprism_core::graph::DependencyType::Direct)?;
537
538 let valid_dependencies: Vec<_> = dependencies
540 .iter()
541 .filter(|dep| is_valid_dependency_node(&dep.target_node))
542 .collect();
543
544 result["dependencies"] = serde_json::json!(valid_dependencies
545 .iter()
546 .map(|dep| {
547 let mut dep_info =
548 create_node_info_with_context(&dep.target_node, context_lines);
549 dep_info["edge_kind"] = serde_json::json!(format!("{:?}", dep.edge_kind));
550 dep_info
551 })
552 .collect::<Vec<_>>());
553 }
554
555 if include_usages {
556 let references = server.graph_query().find_references(&symbol_id)?;
557 result["usages"] = serde_json::json!(references
558 .iter()
559 .map(|ref_| {
560 let mut usage_info =
561 create_node_info_with_context(&ref_.source_node, context_lines);
562 usage_info["edge_kind"] = serde_json::json!(format!("{:?}", ref_.edge_kind));
563 usage_info["reference_location"] = serde_json::json!({
564 "file": ref_.location.file.display().to_string(),
565 "span": {
566 "start_line": ref_.location.span.start_line,
567 "end_line": ref_.location.span.end_line,
568 "start_column": ref_.location.span.start_column,
569 "end_column": ref_.location.span.end_column
570 }
571 });
572 usage_info
573 })
574 .collect::<Vec<_>>());
575 }
576
577 let workflow_suggestions = generate_symbol_workflow_suggestions(symbol_id_str, &node);
579 result["workflow_guidance"] = serde_json::json!({
580 "next_steps": workflow_suggestions.iter().map(|suggestion| {
581 serde_json::json!({
582 "tool": suggestion.tool_name,
583 "parameters": suggestion.suggested_parameters,
584 "confidence": format!("{:?}", suggestion.confidence),
585 "reasoning": suggestion.reasoning,
586 "expected_outcome": suggestion.expected_outcome,
587 "priority": suggestion.priority
588 })
589 }).collect::<Vec<_>>(),
590 "analysis_context": {
591 "symbol_type": format!("{:?}", node.kind),
592 "file": node.file.display().to_string(),
593 "complexity_hint": match node.kind {
594 codeprism_core::NodeKind::Class => "Consider analyzing inheritance relationships and decorator patterns",
595 codeprism_core::NodeKind::Function | codeprism_core::NodeKind::Method => "Consider tracing data flow and analyzing complexity",
596 codeprism_core::NodeKind::Module => "Consider exploring contained symbols and dependencies",
597 _ => "Consider analyzing dependencies and references"
598 }
599 }
600 });
601
602 Ok(CallToolResult {
603 content: vec![ToolContent::Text {
604 text: serde_json::to_string_pretty(&result)?,
605 }],
606 is_error: Some(false),
607 })
608 } else {
609 Ok(CallToolResult {
610 content: vec![ToolContent::Text {
611 text: format!("Symbol not found: {}", symbol_id_str),
612 }],
613 is_error: Some(true),
614 })
615 }
616}
617
618async fn search_symbols(
620 server: &CodePrismMcpServer,
621 arguments: Option<&Value>,
622) -> Result<CallToolResult> {
623 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
624
625 let pattern = args
626 .get("pattern")
627 .and_then(|v| v.as_str())
628 .ok_or_else(|| anyhow::anyhow!("Missing pattern parameter"))?;
629
630 let symbol_types = args
631 .get("symbol_types")
632 .and_then(|v| v.as_array())
633 .map(|arr| {
634 arr.iter()
635 .filter_map(|v| v.as_str())
636 .filter_map(|s| match s {
637 "function" => Some(codeprism_core::NodeKind::Function),
638 "class" => Some(codeprism_core::NodeKind::Class),
639 "variable" => Some(codeprism_core::NodeKind::Variable),
640 "module" => Some(codeprism_core::NodeKind::Module),
641 "method" => Some(codeprism_core::NodeKind::Method),
642 _ => None,
643 })
644 .collect::<Vec<_>>()
645 });
646
647 let inheritance_filters = args
648 .get("inheritance_filters")
649 .and_then(|v| v.as_array())
650 .map(|arr| {
651 arr.iter()
652 .filter_map(|v| v.as_str())
653 .filter_map(parse_inheritance_filter)
654 .collect::<Vec<_>>()
655 });
656
657 let limit = args
658 .get("limit")
659 .and_then(|v| v.as_u64())
660 .map(|v| v as usize);
661
662 let context_lines = args
663 .get("context_lines")
664 .and_then(|v| v.as_u64())
665 .map(|v| v as usize)
666 .unwrap_or(4);
667
668 let results = if let Some(ref filters) = inheritance_filters {
670 server.graph_query().search_symbols_with_inheritance(
671 pattern,
672 symbol_types,
673 Some(filters.clone()),
674 limit,
675 )?
676 } else {
677 server
678 .graph_query()
679 .search_symbols(pattern, symbol_types, limit)?
680 };
681
682 let result = serde_json::json!({
683 "pattern": pattern,
684 "inheritance_filters_applied": inheritance_filters.is_some(),
685 "results": results.iter().map(|symbol| {
686 let mut symbol_info = create_node_info_with_context(&symbol.node, context_lines);
687 symbol_info["references_count"] = serde_json::json!(symbol.references_count);
688 symbol_info["dependencies_count"] = serde_json::json!(symbol.dependencies_count);
689
690 if matches!(symbol.node.kind, codeprism_core::NodeKind::Class) && inheritance_filters.is_some() {
692 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol.node.id) {
693 symbol_info["inheritance_summary"] = serde_json::json!({
694 "is_metaclass": inheritance_info.is_metaclass,
695 "base_classes": inheritance_info.base_classes.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
696 "mixins": inheritance_info.mixins.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
697 "metaclass": inheritance_info.metaclass.as_ref().map(|mc| mc.class_name.clone())
698 });
699 }
700 }
701
702 symbol_info
703 }).collect::<Vec<_>>()
704 });
705
706 Ok(CallToolResult {
707 content: vec![ToolContent::Text {
708 text: serde_json::to_string_pretty(&result)?,
709 }],
710 is_error: Some(false),
711 })
712}