1use crate::tools_legacy::{CallToolParams, CallToolResult, Tool, ToolContent};
7use crate::CodePrismMcpServer;
8use anyhow::Result;
9use codeprism_lang_js::JavaScriptAnalyzer;
10use serde_json::Value;
11use std::path::Path;
12
13pub fn list_tools() -> Vec<Tool> {
15 vec![
16 Tool {
17 name: "analyze_javascript_frameworks".to_string(),
18 title: Some("Analyze JavaScript Frameworks".to_string()),
19 description: "Detect and analyze JavaScript/TypeScript frameworks and libraries in use"
20 .to_string(),
21 input_schema: serde_json::json!({
22 "type": "object",
23 "properties": {
24 "include_confidence": {
25 "type": "boolean",
26 "description": "Include confidence scores for framework detection",
27 "default": true
28 },
29 "analyze_versions": {
30 "type": "boolean",
31 "description": "Attempt to detect framework versions",
32 "default": true
33 },
34 "frameworks": {
35 "type": "array",
36 "items": {
37 "type": "string",
38 "enum": ["react", "vue", "angular", "express", "nextjs", "nuxt", "all"]
39 },
40 "description": "Specific frameworks to analyze",
41 "default": ["all"]
42 }
43 },
44 "required": []
45 }),
46 },
47 Tool {
48 name: "analyze_react_components".to_string(),
49 title: Some("Analyze React Components".to_string()),
50 description: "Deep analysis of React components, hooks, and patterns".to_string(),
51 input_schema: serde_json::json!({
52 "type": "object",
53 "properties": {
54 "include_hooks": {
55 "type": "boolean",
56 "description": "Include React hooks analysis",
57 "default": true
58 },
59 "analyze_props": {
60 "type": "boolean",
61 "description": "Analyze component props and PropTypes",
62 "default": true
63 },
64 "detect_patterns": {
65 "type": "boolean",
66 "description": "Detect React patterns and anti-patterns",
67 "default": true
68 },
69 "include_context": {
70 "type": "boolean",
71 "description": "Analyze React Context usage",
72 "default": true
73 }
74 },
75 "required": []
76 }),
77 },
78 Tool {
79 name: "analyze_nodejs_patterns".to_string(),
80 title: Some("Analyze Node.js Patterns".to_string()),
81 description:
82 "Analyze Node.js backend patterns, database integrations, and architecture"
83 .to_string(),
84 input_schema: serde_json::json!({
85 "type": "object",
86 "properties": {
87 "include_databases": {
88 "type": "boolean",
89 "description": "Include database integration analysis",
90 "default": true
91 },
92 "analyze_routing": {
93 "type": "boolean",
94 "description": "Analyze routing patterns and middleware",
95 "default": true
96 },
97 "detect_orms": {
98 "type": "boolean",
99 "description": "Detect ORM frameworks and patterns",
100 "default": true
101 },
102 "include_security": {
103 "type": "boolean",
104 "description": "Include security pattern analysis",
105 "default": true
106 }
107 },
108 "required": []
109 }),
110 },
111 ]
112}
113
114pub async fn call_tool(
116 server: &CodePrismMcpServer,
117 params: &CallToolParams,
118) -> Result<CallToolResult> {
119 match params.name.as_str() {
120 "analyze_javascript_frameworks" => {
121 analyze_javascript_frameworks(server, params.arguments.as_ref()).await
122 }
123 "analyze_react_components" => {
124 analyze_react_components(server, params.arguments.as_ref()).await
125 }
126 "analyze_nodejs_patterns" => {
127 analyze_nodejs_patterns(server, params.arguments.as_ref()).await
128 }
129 _ => Err(anyhow::anyhow!(
130 "Unknown JavaScript analysis tool: {}",
131 params.name
132 )),
133 }
134}
135
136async fn analyze_javascript_frameworks(
138 server: &CodePrismMcpServer,
139 arguments: Option<&Value>,
140) -> Result<CallToolResult> {
141 let default_args = serde_json::json!({});
142 let args = arguments.unwrap_or(&default_args);
143
144 let include_confidence = args
145 .get("include_confidence")
146 .and_then(|v| v.as_bool())
147 .unwrap_or(true);
148
149 let analyze_versions = args
150 .get("analyze_versions")
151 .and_then(|v| v.as_bool())
152 .unwrap_or(true);
153
154 let frameworks = args
155 .get("frameworks")
156 .and_then(|v| v.as_array())
157 .map(|arr| {
158 arr.iter()
159 .filter_map(|v| v.as_str())
160 .map(|s| s.to_string())
161 .collect::<Vec<_>>()
162 })
163 .unwrap_or_else(|| vec!["all".to_string()]);
164
165 let analyzer = JavaScriptAnalyzer::new();
166 let mut all_frameworks = Vec::new();
167 let mut files_analyzed = 0;
168
169 if let Some(repo_path) = server.repository_path() {
171 match server.scanner().discover_files(repo_path) {
172 Ok(files) => {
173 for file_path in files {
174 if is_javascript_file(&file_path) {
175 match std::fs::read_to_string(&file_path) {
176 Ok(content) => {
177 files_analyzed += 1;
178
179 match analyzer.detect_frameworks(&content) {
181 Ok(frameworks_detected) => {
182 for framework in frameworks_detected {
184 if frameworks.contains(&"all".to_string())
185 || frameworks
186 .contains(&framework.name.to_lowercase())
187 {
188 all_frameworks.push(framework);
189 }
190 }
191 }
192 Err(e) => {
193 eprintln!("Error analyzing {}: {}", file_path.display(), e);
194 }
195 }
196 }
197 Err(e) => {
198 eprintln!("Error reading {}: {}", file_path.display(), e);
199 }
200 }
201 }
202 }
203 }
204 Err(e) => {
205 return Err(anyhow::anyhow!("Failed to discover files: {}", e));
206 }
207 }
208 }
209
210 let mut framework_summary = std::collections::HashMap::new();
212 for framework in &all_frameworks {
213 let entry = framework_summary
214 .entry(framework.name.clone())
215 .or_insert_with(|| {
216 serde_json::json!({
217 "name": framework.name,
218 "confidence": framework.confidence,
219 "files_count": 0,
220 "versions": Vec::<String>::new(),
221 "features": Vec::<String>::new(),
222 "best_practices": Vec::<String>::new()
223 })
224 });
225
226 entry["files_count"] = serde_json::json!(entry["files_count"].as_u64().unwrap_or(0) + 1);
228
229 if include_confidence {
230 let current_confidence = entry["confidence"].as_f64().unwrap_or(0.0);
231 let new_confidence = (current_confidence + framework.confidence as f64) / 2.0;
232 entry["confidence"] = serde_json::json!(new_confidence);
233 }
234
235 for feature in &framework.features_used {
237 if !entry["features"]
238 .as_array()
239 .unwrap()
240 .iter()
241 .any(|f| f.as_str() == Some(feature))
242 {
243 entry["features"]
244 .as_array_mut()
245 .unwrap()
246 .push(serde_json::json!(feature));
247 }
248 }
249
250 for practice in &framework.best_practices {
251 if !entry["best_practices"]
252 .as_array()
253 .unwrap()
254 .iter()
255 .any(|p| p.as_str() == Some(practice))
256 {
257 entry["best_practices"]
258 .as_array_mut()
259 .unwrap()
260 .push(serde_json::json!(practice));
261 }
262 }
263 }
264
265 let result = serde_json::json!({
266 "analysis_summary": {
267 "files_analyzed": files_analyzed,
268 "frameworks_detected": framework_summary.len(),
269 "total_framework_instances": all_frameworks.len()
270 },
271 "frameworks": framework_summary.values().collect::<Vec<_>>(),
272 "analysis_metadata": {
273 "include_confidence": include_confidence,
274 "analyze_versions": analyze_versions,
275 "requested_frameworks": frameworks,
276 "timestamp": chrono::Utc::now().to_rfc3339(),
277 "analyzer_version": "2.1.0"
278 }
279 });
280
281 Ok(CallToolResult {
282 content: vec![ToolContent::Text {
283 text: serde_json::to_string_pretty(&result)?,
284 }],
285 is_error: Some(false),
286 })
287}
288
289async fn analyze_react_components(
291 server: &CodePrismMcpServer,
292 arguments: Option<&Value>,
293) -> Result<CallToolResult> {
294 let default_args = serde_json::json!({});
295 let args = arguments.unwrap_or(&default_args);
296
297 let include_hooks = args
298 .get("include_hooks")
299 .and_then(|v| v.as_bool())
300 .unwrap_or(true);
301
302 let analyze_props = args
303 .get("analyze_props")
304 .and_then(|v| v.as_bool())
305 .unwrap_or(true);
306
307 let _detect_patterns = args
308 .get("detect_patterns")
309 .and_then(|v| v.as_bool())
310 .unwrap_or(true);
311
312 let include_context = args
313 .get("include_context")
314 .and_then(|v| v.as_bool())
315 .unwrap_or(true);
316
317 let analyzer = JavaScriptAnalyzer::new();
318 let mut all_components = Vec::new();
319 let mut files_analyzed = 0;
320
321 if let Some(repo_path) = server.repository_path() {
323 match server.scanner().discover_files(repo_path) {
324 Ok(files) => {
325 for file_path in files {
326 if is_react_file(&file_path) {
327 match std::fs::read_to_string(&file_path) {
328 Ok(content) => {
329 files_analyzed += 1;
330
331 match analyzer.analyze_react_patterns(&content) {
333 Ok(components) => {
334 for component in components {
336 let hooks_data = if include_hooks {
337 component
338 .hooks_used
339 .iter()
340 .map(|h| {
341 serde_json::json!({
342 "name": h.name,
343 "hook_type": h.hook_type,
344 "dependencies": h.dependencies,
345 "custom_hook": h.custom_hook
346 })
347 })
348 .collect::<Vec<_>>()
349 } else {
350 Vec::new()
351 };
352
353 let props_data = if analyze_props {
354 Some(serde_json::json!({
355 "prop_names": component.props_analysis.prop_names,
356 "has_prop_types": component.props_analysis.has_prop_types,
357 "has_default_props": component.props_analysis.has_default_props,
358 "destructured": component.props_analysis.destructured,
359 "typescript_props": component.props_analysis.typescript_props
360 }))
361 } else {
362 None
363 };
364
365 let context_data = if include_context {
366 component
367 .context_usage
368 .iter()
369 .map(|c| {
370 serde_json::json!({
371 "context_name": c.context_name,
372 "usage_type": c.usage_type,
373 "values_consumed": c.values_consumed
374 })
375 })
376 .collect::<Vec<_>>()
377 } else {
378 Vec::new()
379 };
380
381 all_components.push(serde_json::json!({
382 "name": component.name,
383 "type": format!("{:?}", component.component_type),
384 "file": file_path.display().to_string(),
385 "hooks": if include_hooks { Some(hooks_data) } else { None },
386 "props": props_data,
387 "jsx_elements": component.jsx_elements,
388 "lifecycle_methods": component.lifecycle_methods,
389 "context_usage": if include_context { Some(context_data) } else { None }
390 }));
391 }
392 }
393 Err(e) => {
394 eprintln!("Error analyzing {}: {}", file_path.display(), e);
395 }
396 }
397 }
398 Err(e) => {
399 eprintln!("Error reading {}: {}", file_path.display(), e);
400 }
401 }
402 }
403 }
404 }
405 Err(e) => {
406 return Err(anyhow::anyhow!("Failed to discover files: {}", e));
407 }
408 }
409 }
410
411 let hooks_summary = if include_hooks {
413 let mut hook_counts = std::collections::HashMap::new();
414 for component in &all_components {
415 if let Some(hooks) = component.get("hooks").and_then(|h| h.as_array()) {
416 for hook in hooks {
417 if let Some(hook_name) = hook.as_str() {
418 *hook_counts.entry(hook_name.to_string()).or_insert(0) += 1;
419 }
420 }
421 }
422 }
423 Some(hook_counts)
424 } else {
425 None
426 };
427
428 let result = serde_json::json!({
429 "analysis_summary": {
430 "files_analyzed": files_analyzed,
431 "components_found": all_components.len(),
432 "hooks_analysis_included": include_hooks,
433 "props_analysis_included": analyze_props,
434 "context_analysis_included": include_context
435 },
436 "components": all_components,
437 "hooks_summary": hooks_summary,
438 "analysis_metadata": {
439 "timestamp": chrono::Utc::now().to_rfc3339(),
440 "analyzer_version": "2.1.0"
441 }
442 });
443
444 Ok(CallToolResult {
445 content: vec![ToolContent::Text {
446 text: serde_json::to_string_pretty(&result)?,
447 }],
448 is_error: Some(false),
449 })
450}
451
452async fn analyze_nodejs_patterns(
454 server: &CodePrismMcpServer,
455 arguments: Option<&Value>,
456) -> Result<CallToolResult> {
457 let default_args = serde_json::json!({});
458 let args = arguments.unwrap_or(&default_args);
459
460 let include_databases = args
461 .get("include_databases")
462 .and_then(|v| v.as_bool())
463 .unwrap_or(true);
464
465 let analyze_routing = args
466 .get("analyze_routing")
467 .and_then(|v| v.as_bool())
468 .unwrap_or(true);
469
470 let detect_orms = args
471 .get("detect_orms")
472 .and_then(|v| v.as_bool())
473 .unwrap_or(true);
474
475 let include_security = args
476 .get("include_security")
477 .and_then(|v| v.as_bool())
478 .unwrap_or(true);
479
480 let analyzer = JavaScriptAnalyzer::new();
481 let mut nodejs_patterns = Vec::new();
482 let mut files_analyzed = 0;
483
484 if let Some(repo_path) = server.repository_path() {
486 match server.scanner().discover_files(repo_path) {
487 Ok(files) => {
488 for file_path in files {
489 if is_nodejs_file(&file_path) {
490 match std::fs::read_to_string(&file_path) {
491 Ok(content) => {
492 files_analyzed += 1;
493
494 match analyzer.analyze_nodejs_patterns(&content) {
496 Ok(patterns) => {
497 for pattern in patterns {
499 let route_data = pattern.route_info.map(|r| {
500 serde_json::json!({
501 "path": r.path,
502 "method": r.method,
503 "parameters": r.parameters,
504 "query_params": r.query_params,
505 "middleware_used": r.middleware_used
506 })
507 });
508
509 let db_patterns_data = if include_databases {
510 pattern
511 .database_patterns
512 .iter()
513 .map(|db| {
514 serde_json::json!({
515 "db_type": db.db_type,
516 "operations": db.operations,
517 "orm_framework": db.orm_framework
518 })
519 })
520 .collect::<Vec<_>>()
521 } else {
522 Vec::new()
523 };
524
525 nodejs_patterns.push(serde_json::json!({
526 "pattern_type": format!("{:?}", pattern.pattern_type),
527 "file": file_path.display().to_string(),
528 "framework": pattern.framework,
529 "route_info": route_data,
530 "middleware_chain": pattern.middleware_chain,
531 "http_methods": pattern.http_methods,
532 "database_patterns": if include_databases { Some(db_patterns_data) } else { None }
533 }));
534 }
535 }
536 Err(e) => {
537 eprintln!("Error analyzing {}: {}", file_path.display(), e);
538 }
539 }
540 }
541 Err(e) => {
542 eprintln!("Error reading {}: {}", file_path.display(), e);
543 }
544 }
545 }
546 }
547 }
548 Err(e) => {
549 return Err(anyhow::anyhow!("Failed to discover files: {}", e));
550 }
551 }
552 }
553
554 let result = serde_json::json!({
555 "analysis_summary": {
556 "files_analyzed": files_analyzed,
557 "patterns_found": nodejs_patterns.len(),
558 "database_analysis_included": include_databases,
559 "routing_analysis_included": analyze_routing,
560 "orm_analysis_included": detect_orms,
561 "security_analysis_included": include_security
562 },
563 "nodejs_patterns": nodejs_patterns,
564 "analysis_metadata": {
565 "timestamp": chrono::Utc::now().to_rfc3339(),
566 "analyzer_version": "2.1.0"
567 }
568 });
569
570 Ok(CallToolResult {
571 content: vec![ToolContent::Text {
572 text: serde_json::to_string_pretty(&result)?,
573 }],
574 is_error: Some(false),
575 })
576}
577
578fn is_javascript_file(path: &Path) -> bool {
580 if let Some(extension) = path.extension() {
581 matches!(
582 extension.to_str(),
583 Some("js") | Some("jsx") | Some("ts") | Some("tsx") | Some("mjs") | Some("cjs")
584 )
585 } else {
586 false
587 }
588}
589
590fn is_react_file(path: &Path) -> bool {
592 if let Some(extension) = path.extension() {
593 matches!(extension.to_str(), Some("jsx") | Some("tsx"))
594 } else if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
595 name.contains("component") || name.contains("Component") || name.starts_with("use")
596 } else {
597 false
598 }
599}
600
601fn is_nodejs_file(path: &Path) -> bool {
603 if let Some(extension) = path.extension() {
604 matches!(
605 extension.to_str(),
606 Some("js") | Some("ts") | Some("mjs") | Some("cjs")
607 )
608 } else if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
609 matches!(
610 name,
611 "server.js"
612 | "app.js"
613 | "index.js"
614 | "main.js"
615 | "api.js"
616 | "routes.js"
617 | "middleware.js"
618 )
619 } else {
620 false
621 }
622}