Skip to main content

agentic_codebase/mcp/
server.rs

1//! MCP server implementation.
2//!
3//! Synchronous JSON-RPC 2.0 server that exposes code graph operations
4//! through the Model Context Protocol. All operations are in-process
5//! with no async runtime required.
6
7use std::collections::HashMap;
8use std::path::Path;
9
10use serde_json::{json, Value};
11
12use crate::engine::query::{ImpactParams, MatchMode, SymbolLookupParams};
13use crate::engine::QueryEngine;
14use crate::format::reader::AcbReader;
15use crate::graph::CodeGraph;
16use crate::grounding::{Grounded, GroundingEngine, GroundingResult};
17use crate::types::{CodeUnitType, EdgeType};
18use crate::workspace::{ContextRole, TranslationMap, TranslationStatus, WorkspaceManager};
19
20use super::protocol::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
21
22/// MCP server capability information.
23const SERVER_NAME: &str = "agentic-codebase";
24/// MCP server version.
25const SERVER_VERSION: &str = "0.1.0";
26/// MCP protocol version supported.
27const PROTOCOL_VERSION: &str = "2024-11-05";
28
29/// Record of a tool call or analysis context entry.
30#[derive(Debug, Clone)]
31pub struct OperationRecord {
32    pub tool_name: String,
33    pub summary: String,
34    pub timestamp: u64,
35    pub graph_name: Option<String>,
36}
37
38/// A synchronous MCP server that handles JSON-RPC 2.0 messages.
39///
40/// Holds loaded code graphs and dispatches tool/resource/prompt requests
41/// to the appropriate handler.
42#[derive(Debug)]
43pub struct McpServer {
44    /// Loaded code graphs keyed by name.
45    graphs: HashMap<String, CodeGraph>,
46    /// Query engine for executing queries.
47    engine: QueryEngine,
48    /// Whether the server has been initialised.
49    initialized: bool,
50    /// Log of operations with context for this session.
51    operation_log: Vec<OperationRecord>,
52    /// Timestamp when this session started.
53    session_start_time: Option<u64>,
54    /// Multi-context workspace manager.
55    workspace_manager: WorkspaceManager,
56    /// Translation maps keyed by workspace ID.
57    translation_maps: HashMap<String, TranslationMap>,
58    /// Deferred graph path for lazy loading on first tool call.
59    deferred_graph: Option<(String, String)>,
60}
61
62impl McpServer {
63    fn parse_unit_type(raw: &str) -> Option<CodeUnitType> {
64        match raw.trim().to_ascii_lowercase().as_str() {
65            "module" | "modules" => Some(CodeUnitType::Module),
66            "symbol" | "symbols" => Some(CodeUnitType::Symbol),
67            "type" | "types" => Some(CodeUnitType::Type),
68            "function" | "functions" => Some(CodeUnitType::Function),
69            "parameter" | "parameters" => Some(CodeUnitType::Parameter),
70            "import" | "imports" => Some(CodeUnitType::Import),
71            "test" | "tests" => Some(CodeUnitType::Test),
72            "doc" | "docs" | "document" | "documents" => Some(CodeUnitType::Doc),
73            "config" | "configs" => Some(CodeUnitType::Config),
74            "pattern" | "patterns" => Some(CodeUnitType::Pattern),
75            "trait" | "traits" => Some(CodeUnitType::Trait),
76            "impl" | "implementation" | "implementations" => Some(CodeUnitType::Impl),
77            "macro" | "macros" => Some(CodeUnitType::Macro),
78            _ => None,
79        }
80    }
81
82    /// Create a new MCP server with no loaded graphs.
83    pub fn new() -> Self {
84        Self {
85            graphs: HashMap::new(),
86            engine: QueryEngine::new(),
87            initialized: false,
88            operation_log: Vec::new(),
89            session_start_time: None,
90            workspace_manager: WorkspaceManager::new(),
91            translation_maps: HashMap::new(),
92            deferred_graph: None,
93        }
94    }
95
96    /// Load a code graph into the server under the given name.
97    pub fn load_graph(&mut self, name: String, graph: CodeGraph) {
98        self.graphs.insert(name, graph);
99    }
100
101    /// Set a deferred graph path for lazy loading.
102    ///
103    /// If no graphs are loaded when a tool is called, the server will
104    /// attempt to load this graph automatically. This provides a safety
105    /// net when startup auto-resolve fails (e.g. wrong CWD).
106    pub fn set_deferred_graph(&mut self, name: String, path: String) {
107        self.deferred_graph = Some((name, path));
108    }
109
110    /// Attempt to lazy-load the deferred graph. Called automatically
111    /// before tool dispatch when no graphs are loaded.
112    fn try_lazy_load(&mut self) {
113        if let Some((name, path)) = self.deferred_graph.take() {
114            match AcbReader::read_from_file(Path::new(&path)) {
115                Ok(graph) => {
116                    self.graphs.insert(name, graph);
117                }
118                Err(_) => {
119                    // Re-store the deferred path so we don't lose it
120                    self.deferred_graph = Some((name, path));
121                }
122            }
123        }
124    }
125
126    /// Remove a loaded code graph.
127    pub fn unload_graph(&mut self, name: &str) -> Option<CodeGraph> {
128        self.graphs.remove(name)
129    }
130
131    /// Get a reference to a loaded graph by name.
132    pub fn get_graph(&self, name: &str) -> Option<&CodeGraph> {
133        self.graphs.get(name)
134    }
135
136    /// List all loaded graph names.
137    pub fn graph_names(&self) -> Vec<&str> {
138        self.graphs.keys().map(|s| s.as_str()).collect()
139    }
140
141    /// Check if the server has been initialised.
142    pub fn is_initialized(&self) -> bool {
143        self.initialized
144    }
145
146    /// Handle a raw JSON-RPC message string.
147    ///
148    /// Parses the message, dispatches to the appropriate handler, and
149    /// returns the serialised JSON-RPC response.
150    pub fn handle_raw(&mut self, raw: &str) -> String {
151        let response = match super::protocol::parse_request(raw) {
152            Ok(request) => {
153                if request.id.is_none() {
154                    self.handle_notification(&request.method, &request.params);
155                    return String::new();
156                }
157                self.handle_request(request)
158            }
159            Err(error_response) => error_response,
160        };
161        serde_json::to_string(&response).unwrap_or_else(|_| {
162            r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Serialization failed"}}"#
163                .to_string()
164        })
165    }
166
167    /// Handle a parsed JSON-RPC request.
168    pub fn handle_request(&mut self, request: JsonRpcRequest) -> JsonRpcResponse {
169        let id = request.id.clone().unwrap_or(Value::Null);
170        match request.method.as_str() {
171            "initialize" => self.handle_initialize(id, &request.params),
172            "shutdown" => self.handle_shutdown(id),
173            "tools/list" => self.handle_tools_list(id),
174            "tools/call" => self.handle_tools_call(id, &request.params),
175            "resources/list" => self.handle_resources_list(id),
176            "resources/read" => self.handle_resources_read(id, &request.params),
177            "prompts/list" => self.handle_prompts_list(id),
178            _ => JsonRpcResponse::error(id, JsonRpcError::method_not_found(&request.method)),
179        }
180    }
181
182    /// Handle JSON-RPC notifications (messages without an `id`).
183    ///
184    /// Notification methods intentionally produce no response frame.
185    fn handle_notification(&mut self, method: &str, _params: &Value) {
186        if method == "notifications/initialized" {
187            self.initialized = true;
188        }
189    }
190
191    // ========================================================================
192    // Method handlers
193    // ========================================================================
194
195    /// Handle the "initialize" method.
196    fn handle_initialize(&mut self, id: Value, _params: &Value) -> JsonRpcResponse {
197        self.initialized = true;
198        self.session_start_time = Some(
199            std::time::SystemTime::now()
200                .duration_since(std::time::UNIX_EPOCH)
201                .unwrap_or_default()
202                .as_secs(),
203        );
204        self.operation_log.clear();
205        JsonRpcResponse::success(
206            id,
207            json!({
208                "protocolVersion": PROTOCOL_VERSION,
209                "capabilities": {
210                    "tools": { "listChanged": false },
211                    "resources": { "subscribe": false, "listChanged": false },
212                    "prompts": { "listChanged": false }
213                },
214                "serverInfo": {
215                    "name": SERVER_NAME,
216                    "version": SERVER_VERSION
217                }
218            }),
219        )
220    }
221
222    /// Handle the "shutdown" method.
223    fn handle_shutdown(&mut self, id: Value) -> JsonRpcResponse {
224        self.initialized = false;
225        JsonRpcResponse::success(id, json!(null))
226    }
227
228    /// Handle "tools/list".
229    fn handle_tools_list(&self, id: Value) -> JsonRpcResponse {
230        JsonRpcResponse::success(
231            id,
232            json!({
233                "tools": [
234                    {
235                        "name": "symbol_lookup",
236                        "description": "Look up symbols by name in the code graph",
237                        "inputSchema": {
238                            "type": "object",
239                            "properties": {
240                                "graph": { "type": "string", "description": "Graph name" },
241                                "name": { "type": "string", "description": "Symbol name to search for" },
242                                "mode": { "type": "string", "enum": ["exact", "prefix", "contains", "fuzzy"], "default": "prefix" },
243                                "limit": { "type": "integer", "minimum": 1, "default": 10 }
244                            },
245                            "required": ["name"]
246                        }
247                    },
248                    {
249                        "name": "impact_analysis",
250                        "description": "Analyse the impact of changing a code unit",
251                        "inputSchema": {
252                            "type": "object",
253                            "properties": {
254                                "graph": { "type": "string", "description": "Graph name" },
255                                "unit_id": { "type": "integer", "description": "Code unit ID to analyse" },
256                                "max_depth": { "type": "integer", "minimum": 0, "default": 3 }
257                            },
258                            "required": ["unit_id"]
259                        }
260                    },
261                    {
262                        "name": "graph_stats",
263                        "description": "Get summary statistics about a loaded code graph",
264                        "inputSchema": {
265                            "type": "object",
266                            "properties": {
267                                "graph": { "type": "string", "description": "Graph name" }
268                            }
269                        }
270                    },
271                    {
272                        "name": "list_units",
273                        "description": "List code units in a graph, optionally filtered by type",
274                        "inputSchema": {
275                            "type": "object",
276                            "properties": {
277                                "graph": { "type": "string", "description": "Graph name" },
278                                "unit_type": {
279                                    "type": "string",
280                                    "description": "Filter by unit type",
281                                    "enum": [
282                                        "module", "symbol", "type", "function", "parameter", "import",
283                                        "test", "doc", "config", "pattern", "trait", "impl", "macro"
284                                    ]
285                                },
286                                "limit": { "type": "integer", "default": 50 }
287                            }
288                        }
289                    },
290                    {
291                        "name": "analysis_log",
292                        "description": "Log the intent and context behind a code analysis. Call this to record WHY you are performing a lookup or analysis",
293                        "inputSchema": {
294                            "type": "object",
295                            "properties": {
296                                "intent": {
297                                    "type": "string",
298                                    "description": "Why you are analysing — the goal or reason for the code query"
299                                },
300                                "finding": {
301                                    "type": "string",
302                                    "description": "What you found or concluded from the analysis"
303                                },
304                                "graph": {
305                                    "type": "string",
306                                    "description": "Optional graph name this analysis relates to"
307                                },
308                                "topic": {
309                                    "type": "string",
310                                    "description": "Optional topic or category (e.g., 'refactoring', 'bug-hunt')"
311                                }
312                            },
313                            "required": ["intent"]
314                        }
315                    },
316                    // ── Grounding tools ──────────────────────────────────
317                    {
318                        "name": "codebase_ground",
319                        "description": "Verify a claim about code has graph evidence. Use before asserting code exists",
320                        "inputSchema": {
321                            "type": "object",
322                            "properties": {
323                                "claim": { "type": "string", "description": "The claim to verify (e.g., 'function validate_token exists')" },
324                                "graph": { "type": "string", "description": "Graph name" },
325                                "strict": { "type": "boolean", "description": "If true, partial matches return Ungrounded (default: false)", "default": false }
326                            },
327                            "required": ["claim"]
328                        }
329                    },
330                    {
331                        "name": "codebase_evidence",
332                        "description": "Get graph evidence for a symbol name",
333                        "inputSchema": {
334                            "type": "object",
335                            "properties": {
336                                "name": { "type": "string", "description": "Symbol name to find" },
337                                "graph": { "type": "string", "description": "Graph name" },
338                                "types": {
339                                    "type": "array",
340                                    "items": { "type": "string" },
341                                    "description": "Filter by type: function, struct, enum, module, trait (optional)"
342                                }
343                            },
344                            "required": ["name"]
345                        }
346                    },
347                    {
348                        "name": "codebase_suggest",
349                        "description": "Find symbols similar to a name (for corrections)",
350                        "inputSchema": {
351                            "type": "object",
352                            "properties": {
353                                "name": { "type": "string", "description": "Name to find similar matches for" },
354                                "graph": { "type": "string", "description": "Graph name" },
355                                "limit": { "type": "integer", "minimum": 1, "default": 5, "description": "Max suggestions (default: 5)" }
356                            },
357                            "required": ["name"]
358                        }
359                    },
360                    // ── Workspace tools ──────────────────────────────────
361                    {
362                        "name": "workspace_create",
363                        "description": "Create a workspace to load multiple codebases",
364                        "inputSchema": {
365                            "type": "object",
366                            "properties": {
367                                "name": { "type": "string", "description": "Workspace name (e.g., 'cpp-to-rust-migration')" }
368                            },
369                            "required": ["name"]
370                        }
371                    },
372                    {
373                        "name": "workspace_add",
374                        "description": "Add a codebase to an existing workspace",
375                        "inputSchema": {
376                            "type": "object",
377                            "properties": {
378                                "workspace": { "type": "string", "description": "Workspace name or id" },
379                                "graph": { "type": "string", "description": "Name of a loaded graph to add" },
380                                "path": { "type": "string", "description": "Path label for this codebase" },
381                                "role": { "type": "string", "enum": ["source", "target", "reference", "comparison"], "description": "Role of this codebase" },
382                                "language": { "type": "string", "description": "Optional language hint" }
383                            },
384                            "required": ["workspace", "graph", "role"]
385                        }
386                    },
387                    {
388                        "name": "workspace_list",
389                        "description": "List all contexts in a workspace",
390                        "inputSchema": {
391                            "type": "object",
392                            "properties": {
393                                "workspace": { "type": "string", "description": "Workspace name or id" }
394                            },
395                            "required": ["workspace"]
396                        }
397                    },
398                    {
399                        "name": "workspace_query",
400                        "description": "Search across all codebases in workspace",
401                        "inputSchema": {
402                            "type": "object",
403                            "properties": {
404                                "workspace": { "type": "string", "description": "Workspace name or id" },
405                                "query": { "type": "string", "description": "Search query" },
406                                "roles": { "type": "array", "items": { "type": "string" }, "description": "Filter by role (optional)" }
407                            },
408                            "required": ["workspace", "query"]
409                        }
410                    },
411                    {
412                        "name": "workspace_compare",
413                        "description": "Compare a symbol between source and target",
414                        "inputSchema": {
415                            "type": "object",
416                            "properties": {
417                                "workspace": { "type": "string", "description": "Workspace name or id" },
418                                "symbol": { "type": "string", "description": "Symbol to compare" }
419                            },
420                            "required": ["workspace", "symbol"]
421                        }
422                    },
423                    {
424                        "name": "workspace_xref",
425                        "description": "Find where symbol exists/doesn't exist across contexts",
426                        "inputSchema": {
427                            "type": "object",
428                            "properties": {
429                                "workspace": { "type": "string", "description": "Workspace name or id" },
430                                "symbol": { "type": "string", "description": "Symbol to find" }
431                            },
432                            "required": ["workspace", "symbol"]
433                        }
434                    },
435                    // ── Translation tools ────────────────────────────────
436                    {
437                        "name": "translation_record",
438                        "description": "Record source→target symbol mapping",
439                        "inputSchema": {
440                            "type": "object",
441                            "properties": {
442                                "workspace": { "type": "string", "description": "Workspace name or id" },
443                                "source_symbol": { "type": "string", "description": "Symbol in source codebase" },
444                                "target_symbol": { "type": "string", "description": "Symbol in target (null if not ported)" },
445                                "status": { "type": "string", "enum": ["not_started", "in_progress", "ported", "verified", "skipped"], "description": "Porting status" },
446                                "notes": { "type": "string", "description": "Optional notes" }
447                            },
448                            "required": ["workspace", "source_symbol", "status"]
449                        }
450                    },
451                    {
452                        "name": "translation_progress",
453                        "description": "Get migration progress statistics",
454                        "inputSchema": {
455                            "type": "object",
456                            "properties": {
457                                "workspace": { "type": "string", "description": "Workspace name or id" }
458                            },
459                            "required": ["workspace"]
460                        }
461                    },
462                    {
463                        "name": "translation_remaining",
464                        "description": "List symbols not yet ported",
465                        "inputSchema": {
466                            "type": "object",
467                            "properties": {
468                                "workspace": { "type": "string", "description": "Workspace name or id" },
469                                "module": { "type": "string", "description": "Filter by module (optional)" }
470                            },
471                            "required": ["workspace"]
472                        }
473                    },
474                    // ── Invention 1: Enhanced Impact Analysis ────────────
475                    {
476                        "name": "impact_analyze",
477                        "description": "Analyze the full impact of a proposed code change with blast radius and risk assessment",
478                        "inputSchema": {
479                            "type": "object",
480                            "properties": {
481                                "graph": { "type": "string", "description": "Graph name" },
482                                "unit_id": { "type": "integer", "description": "Target code unit ID" },
483                                "change_type": { "type": "string", "enum": ["signature", "behavior", "deletion", "rename", "move"], "default": "behavior" },
484                                "max_depth": { "type": "integer", "minimum": 1, "default": 5 }
485                            },
486                            "required": ["unit_id"]
487                        }
488                    },
489                    {
490                        "name": "impact_path",
491                        "description": "Find the impact path between two code units",
492                        "inputSchema": {
493                            "type": "object",
494                            "properties": {
495                                "graph": { "type": "string", "description": "Graph name" },
496                                "from": { "type": "integer", "description": "Source unit ID" },
497                                "to": { "type": "integer", "description": "Target unit ID" }
498                            },
499                            "required": ["from", "to"]
500                        }
501                    },
502                    // ── Invention 2: Enhanced Code Prophecy ──────────────
503                    {
504                        "name": "prophecy",
505                        "description": "Predict the future of a code unit based on history, complexity, and dependencies",
506                        "inputSchema": {
507                            "type": "object",
508                            "properties": {
509                                "graph": { "type": "string", "description": "Graph name" },
510                                "unit_id": { "type": "integer", "description": "Code unit ID to predict" }
511                            },
512                            "required": ["unit_id"]
513                        }
514                    },
515                    {
516                        "name": "prophecy_if",
517                        "description": "What-if scenario: predict impact of a hypothetical change",
518                        "inputSchema": {
519                            "type": "object",
520                            "properties": {
521                                "graph": { "type": "string", "description": "Graph name" },
522                                "unit_id": { "type": "integer", "description": "Code unit ID" },
523                                "change_type": { "type": "string", "enum": ["signature", "behavior", "deletion", "rename", "move"], "default": "behavior" }
524                            },
525                            "required": ["unit_id"]
526                        }
527                    },
528                    // ── Invention 3: Regression Oracle ───────────────────
529                    {
530                        "name": "regression_predict",
531                        "description": "Predict which tests are most likely affected by a change",
532                        "inputSchema": {
533                            "type": "object",
534                            "properties": {
535                                "graph": { "type": "string", "description": "Graph name" },
536                                "unit_id": { "type": "integer", "description": "Changed code unit ID" },
537                                "max_depth": { "type": "integer", "minimum": 1, "default": 5 }
538                            },
539                            "required": ["unit_id"]
540                        }
541                    },
542                    {
543                        "name": "regression_minimal",
544                        "description": "Get the minimal test set needed for a change",
545                        "inputSchema": {
546                            "type": "object",
547                            "properties": {
548                                "graph": { "type": "string", "description": "Graph name" },
549                                "unit_id": { "type": "integer", "description": "Changed code unit ID" },
550                                "threshold": { "type": "number", "description": "Minimum probability threshold (0.0-1.0)", "default": 0.5 }
551                            },
552                            "required": ["unit_id"]
553                        }
554                    },
555                    // ── Invention 4: Citation Engine ─────────────────────
556                    {
557                        "name": "codebase_ground_claim",
558                        "description": "Ground a claim with full citations including file locations and code snippets",
559                        "inputSchema": {
560                            "type": "object",
561                            "properties": {
562                                "graph": { "type": "string", "description": "Graph name" },
563                                "claim": { "type": "string", "description": "The claim to verify and cite" }
564                            },
565                            "required": ["claim"]
566                        }
567                    },
568                    {
569                        "name": "codebase_cite",
570                        "description": "Get a citation for a specific code unit",
571                        "inputSchema": {
572                            "type": "object",
573                            "properties": {
574                                "graph": { "type": "string", "description": "Graph name" },
575                                "unit_id": { "type": "integer", "description": "Code unit ID to cite" }
576                            },
577                            "required": ["unit_id"]
578                        }
579                    },
580                    // ── Invention 5: Hallucination Detector ──────────────
581                    {
582                        "name": "hallucination_check",
583                        "description": "Check AI-generated output for hallucinations about code",
584                        "inputSchema": {
585                            "type": "object",
586                            "properties": {
587                                "graph": { "type": "string", "description": "Graph name" },
588                                "output": { "type": "string", "description": "AI-generated text to check" }
589                            },
590                            "required": ["output"]
591                        }
592                    },
593                    // ── Invention 6: Truth Maintenance ───────────────────
594                    {
595                        "name": "truth_register",
596                        "description": "Register a truth claim for ongoing maintenance",
597                        "inputSchema": {
598                            "type": "object",
599                            "properties": {
600                                "graph": { "type": "string", "description": "Graph name" },
601                                "claim": { "type": "string", "description": "The truth claim to maintain" }
602                            },
603                            "required": ["claim"]
604                        }
605                    },
606                    {
607                        "name": "truth_check",
608                        "description": "Check if a registered truth is still valid",
609                        "inputSchema": {
610                            "type": "object",
611                            "properties": {
612                                "graph": { "type": "string", "description": "Graph name" },
613                                "claim": { "type": "string", "description": "The truth claim to check" }
614                            },
615                            "required": ["claim"]
616                        }
617                    },
618                    // ── Invention 7: Concept Navigation ──────────────────
619                    {
620                        "name": "concept_find",
621                        "description": "Find code implementing a concept (e.g., authentication, payment)",
622                        "inputSchema": {
623                            "type": "object",
624                            "properties": {
625                                "graph": { "type": "string", "description": "Graph name" },
626                                "concept": { "type": "string", "description": "Concept to find (e.g., 'authentication', 'payment')" }
627                            },
628                            "required": ["concept"]
629                        }
630                    },
631                    {
632                        "name": "concept_map",
633                        "description": "Map all detected concepts in the codebase",
634                        "inputSchema": {
635                            "type": "object",
636                            "properties": {
637                                "graph": { "type": "string", "description": "Graph name" }
638                            }
639                        }
640                    },
641                    {
642                        "name": "concept_explain",
643                        "description": "Explain how a concept is implemented with details",
644                        "inputSchema": {
645                            "type": "object",
646                            "properties": {
647                                "graph": { "type": "string", "description": "Graph name" },
648                                "concept": { "type": "string", "description": "Concept to explain" }
649                            },
650                            "required": ["concept"]
651                        }
652                    },
653                    // ── Invention 8: Architecture Inference ──────────────
654                    {
655                        "name": "architecture_infer",
656                        "description": "Infer the architecture pattern of the codebase",
657                        "inputSchema": {
658                            "type": "object",
659                            "properties": {
660                                "graph": { "type": "string", "description": "Graph name" }
661                            }
662                        }
663                    },
664                    {
665                        "name": "architecture_validate",
666                        "description": "Validate the codebase against its inferred architecture",
667                        "inputSchema": {
668                            "type": "object",
669                            "properties": {
670                                "graph": { "type": "string", "description": "Graph name" }
671                            }
672                        }
673                    },
674                    // ── Invention 9: Semantic Search ─────────────────────
675                    {
676                        "name": "search_semantic",
677                        "description": "Natural-language semantic search across the codebase",
678                        "inputSchema": {
679                            "type": "object",
680                            "properties": {
681                                "graph": { "type": "string", "description": "Graph name" },
682                                "query": { "type": "string", "description": "Natural-language search query" },
683                                "top_k": { "type": "integer", "minimum": 1, "default": 10 }
684                            },
685                            "required": ["query"]
686                        }
687                    },
688                    {
689                        "name": "search_similar",
690                        "description": "Find code units similar to a given unit",
691                        "inputSchema": {
692                            "type": "object",
693                            "properties": {
694                                "graph": { "type": "string", "description": "Graph name" },
695                                "unit_id": { "type": "integer", "description": "Unit ID to find similar units for" },
696                                "top_k": { "type": "integer", "minimum": 1, "default": 10 }
697                            },
698                            "required": ["unit_id"]
699                        }
700                    },
701                    {
702                        "name": "search_explain",
703                        "description": "Explain why a unit matched a search query",
704                        "inputSchema": {
705                            "type": "object",
706                            "properties": {
707                                "graph": { "type": "string", "description": "Graph name" },
708                                "unit_id": { "type": "integer", "description": "Unit ID" },
709                                "query": { "type": "string", "description": "The search query" }
710                            },
711                            "required": ["unit_id", "query"]
712                        }
713                    },
714                    // ── Invention 10: Multi-Codebase Compare ─────────────
715                    {
716                        "name": "compare_codebases",
717                        "description": "Full structural, conceptual, and pattern comparison between two codebases in a workspace",
718                        "inputSchema": {
719                            "type": "object",
720                            "properties": {
721                                "workspace": { "type": "string", "description": "Workspace name or id" }
722                            },
723                            "required": ["workspace"]
724                        }
725                    },
726                    {
727                        "name": "compare_concept",
728                        "description": "Compare how a concept is implemented across two codebases",
729                        "inputSchema": {
730                            "type": "object",
731                            "properties": {
732                                "workspace": { "type": "string", "description": "Workspace name or id" },
733                                "concept": { "type": "string", "description": "Concept to compare (e.g., 'authentication')" }
734                            },
735                            "required": ["workspace", "concept"]
736                        }
737                    },
738                    {
739                        "name": "compare_migrate",
740                        "description": "Generate a migration plan from source to target codebase",
741                        "inputSchema": {
742                            "type": "object",
743                            "properties": {
744                                "workspace": { "type": "string", "description": "Workspace name or id" }
745                            },
746                            "required": ["workspace"]
747                        }
748                    },
749                    // ── Invention 11: Version Archaeology ────────────────
750                    {
751                        "name": "archaeology_node",
752                        "description": "Investigate the full history and evolution of a code unit",
753                        "inputSchema": {
754                            "type": "object",
755                            "properties": {
756                                "graph": { "type": "string", "description": "Graph name" },
757                                "unit_id": { "type": "integer", "description": "Code unit ID to investigate" }
758                            },
759                            "required": ["unit_id"]
760                        }
761                    },
762                    {
763                        "name": "archaeology_why",
764                        "description": "Explain why code looks the way it does based on its history",
765                        "inputSchema": {
766                            "type": "object",
767                            "properties": {
768                                "graph": { "type": "string", "description": "Graph name" },
769                                "unit_id": { "type": "integer", "description": "Code unit ID" }
770                            },
771                            "required": ["unit_id"]
772                        }
773                    },
774                    {
775                        "name": "archaeology_when",
776                        "description": "Get the timeline of changes for a code unit",
777                        "inputSchema": {
778                            "type": "object",
779                            "properties": {
780                                "graph": { "type": "string", "description": "Graph name" },
781                                "unit_id": { "type": "integer", "description": "Code unit ID" }
782                            },
783                            "required": ["unit_id"]
784                        }
785                    },
786                    // ── Invention 12: Pattern Extraction ─────────────────
787                    {
788                        "name": "pattern_extract",
789                        "description": "Extract all detected patterns from the codebase",
790                        "inputSchema": {
791                            "type": "object",
792                            "properties": {
793                                "graph": { "type": "string", "description": "Graph name" }
794                            }
795                        }
796                    },
797                    {
798                        "name": "pattern_check",
799                        "description": "Check a code unit against detected patterns for violations",
800                        "inputSchema": {
801                            "type": "object",
802                            "properties": {
803                                "graph": { "type": "string", "description": "Graph name" },
804                                "unit_id": { "type": "integer", "description": "Code unit ID to check" }
805                            },
806                            "required": ["unit_id"]
807                        }
808                    },
809                    {
810                        "name": "pattern_suggest",
811                        "description": "Suggest patterns for new code based on file location",
812                        "inputSchema": {
813                            "type": "object",
814                            "properties": {
815                                "graph": { "type": "string", "description": "Graph name" },
816                                "file_path": { "type": "string", "description": "File path for pattern suggestions" }
817                            },
818                            "required": ["file_path"]
819                        }
820                    },
821                    // -- Invention 13: Code Resurrection --------------------
822                    { "name": "resurrect_search", "description": "Search for traces of deleted code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "query": { "type": "string", "description": "Search query for deleted code traces" }, "max_results": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["query"] } },
823                    { "name": "resurrect_attempt", "description": "Attempt to reconstruct deleted code from traces", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "query": { "type": "string", "description": "Description of the code to resurrect" } }, "required": ["query"] } },
824                    { "name": "resurrect_verify", "description": "Verify a resurrection attempt is accurate", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "original_name": { "type": "string", "description": "Original name of the deleted code" }, "reconstructed": { "type": "string", "description": "Reconstructed code to verify" } }, "required": ["original_name", "reconstructed"] } },
825                    { "name": "resurrect_history", "description": "Get resurrection history for the codebase", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" } } } },
826                    // -- Invention 14: Code Genetics -----------------------
827                    { "name": "genetics_dna", "description": "Extract the DNA (core patterns) of a code unit", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
828                    { "name": "genetics_lineage", "description": "Trace the lineage of a code unit through evolution", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" }, "max_depth": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["unit_id"] } },
829                    { "name": "genetics_mutations", "description": "Detect mutations (unexpected changes) in code patterns", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
830                    { "name": "genetics_diseases", "description": "Diagnose inherited code diseases (anti-patterns passed through lineage)", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
831                    // -- Invention 15: Code Telepathy ----------------------
832                    { "name": "telepathy_connect", "description": "Establish telepathic connection between codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "source_graph": { "type": "string", "description": "Source graph name" }, "target_graph": { "type": "string", "description": "Target graph name" } }, "required": ["workspace"] } },
833                    { "name": "telepathy_broadcast", "description": "Broadcast a code insight to connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "insight": { "type": "string", "description": "The code insight to broadcast" }, "source_graph": { "type": "string", "description": "Source graph name" } }, "required": ["workspace", "insight"] } },
834                    { "name": "telepathy_listen", "description": "Listen for insights from connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "target_graph": { "type": "string", "description": "Target graph name to listen from" } }, "required": ["workspace"] } },
835                    { "name": "telepathy_consensus", "description": "Find consensus patterns across connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "concept": { "type": "string", "description": "Concept to find consensus on" } }, "required": ["workspace", "concept"] } },
836                    // -- Invention 16: Code Soul --------------------------
837                    { "name": "soul_extract", "description": "Extract the soul (essential purpose and values) of code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
838                    { "name": "soul_compare", "description": "Compare souls across code reincarnations", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id_a": { "type": "integer", "description": "First code unit ID" }, "unit_id_b": { "type": "integer", "description": "Second code unit ID" } }, "required": ["unit_id_a", "unit_id_b"] } },
839                    { "name": "soul_preserve", "description": "Preserve a code soul during rewrite", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" }, "new_language": { "type": "string", "description": "Target language for rewrite" } }, "required": ["unit_id"] } },
840                    { "name": "soul_reincarnate", "description": "Guide a soul to a new code manifestation", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "soul_id": { "type": "string", "description": "Soul identifier" }, "target_context": { "type": "string", "description": "Target context for reincarnation" } }, "required": ["soul_id", "target_context"] } },
841                    { "name": "soul_karma", "description": "Analyze the karma (positive/negative impact history) of code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
842                    // -- Invention 17: Code Omniscience --------------------
843                    { "name": "omniscience_search", "description": "Search across global code knowledge", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Filter by languages" }, "max_results": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["query"] } },
844                    { "name": "omniscience_best", "description": "Find the best implementation of a concept globally", "inputSchema": { "type": "object", "properties": { "capability": { "type": "string", "description": "Capability to find best implementation for" }, "criteria": { "type": "array", "items": { "type": "string" }, "description": "Evaluation criteria" } }, "required": ["capability"] } },
845                    { "name": "omniscience_census", "description": "Global code census for a concept", "inputSchema": { "type": "object", "properties": { "concept": { "type": "string", "description": "Concept to census" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Filter by languages" } }, "required": ["concept"] } },
846                    { "name": "omniscience_vuln", "description": "Scan for known vulnerability patterns", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "pattern": { "type": "string", "description": "Vulnerability pattern to scan for" }, "cve": { "type": "string", "description": "CVE identifier to check" } } } },
847                    { "name": "omniscience_trend", "description": "Find emerging or declining code patterns", "inputSchema": { "type": "object", "properties": { "domain": { "type": "string", "description": "Domain to analyze trends in" }, "threshold": { "type": "number", "default": 0.5 } }, "required": ["domain"] } },
848                    { "name": "omniscience_compare", "description": "Compare your code to global best practices", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID to compare" } }, "required": ["unit_id"] } },
849                    { "name": "omniscience_api_usage", "description": "Find all usages of an API globally", "inputSchema": { "type": "object", "properties": { "api": { "type": "string", "description": "API name to search for" }, "method": { "type": "string", "description": "Specific method within the API" } }, "required": ["api"] } },
850                    { "name": "omniscience_solve", "description": "Find code that solves a specific problem", "inputSchema": { "type": "object", "properties": { "problem": { "type": "string", "description": "Problem description to solve" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Preferred languages" }, "max_results": { "type": "integer", "minimum": 1, "default": 5 } }, "required": ["problem"] } }
851                ]
852            }),
853        )
854    }
855
856    /// Handle "tools/call".
857    fn handle_tools_call(&mut self, id: Value, params: &Value) -> JsonRpcResponse {
858        // Lazy auto-load: if no graphs are loaded, try the deferred path.
859        if self.graphs.is_empty() {
860            self.try_lazy_load();
861        }
862
863        let tool_name = match params.get("name").and_then(|v| v.as_str()) {
864            Some(name) => name,
865            None => {
866                return JsonRpcResponse::error(
867                    id,
868                    JsonRpcError::invalid_params("Missing 'name' field in tools/call params"),
869                );
870            }
871        };
872
873        let arguments = params
874            .get("arguments")
875            .cloned()
876            .unwrap_or(Value::Object(serde_json::Map::new()));
877
878        let result = match tool_name {
879            "symbol_lookup" => self.tool_symbol_lookup(id.clone(), &arguments),
880            "impact_analysis" => self.tool_impact_analysis(id.clone(), &arguments),
881            "graph_stats" => self.tool_graph_stats(id.clone(), &arguments),
882            "list_units" => self.tool_list_units(id.clone(), &arguments),
883            "analysis_log" => return self.tool_analysis_log(id, &arguments),
884            // Grounding tools
885            "codebase_ground" => self.tool_codebase_ground(id.clone(), &arguments),
886            "codebase_evidence" => self.tool_codebase_evidence(id.clone(), &arguments),
887            "codebase_suggest" => self.tool_codebase_suggest(id.clone(), &arguments),
888            // Workspace tools
889            "workspace_create" => return self.tool_workspace_create(id, &arguments),
890            "workspace_add" => return self.tool_workspace_add(id, &arguments),
891            "workspace_list" => self.tool_workspace_list(id.clone(), &arguments),
892            "workspace_query" => self.tool_workspace_query(id.clone(), &arguments),
893            "workspace_compare" => self.tool_workspace_compare(id.clone(), &arguments),
894            "workspace_xref" => self.tool_workspace_xref(id.clone(), &arguments),
895            // Translation tools
896            "translation_record" => return self.tool_translation_record(id, &arguments),
897            "translation_progress" => self.tool_translation_progress(id.clone(), &arguments),
898            "translation_remaining" => self.tool_translation_remaining(id.clone(), &arguments),
899            // ── Invention tools ──────────────────────────────────────
900            // 1. Enhanced Impact Analysis
901            "impact_analyze" => self.tool_impact_analyze(id.clone(), &arguments),
902            "impact_path" => self.tool_impact_path(id.clone(), &arguments),
903            // 2. Enhanced Code Prophecy
904            "prophecy" => self.tool_prophecy(id.clone(), &arguments),
905            "prophecy_if" => self.tool_prophecy_if(id.clone(), &arguments),
906            // 3. Regression Oracle
907            "regression_predict" => self.tool_regression_predict(id.clone(), &arguments),
908            "regression_minimal" => self.tool_regression_minimal(id.clone(), &arguments),
909            // 4. Citation Engine
910            "codebase_ground_claim" => self.tool_codebase_ground_claim(id.clone(), &arguments),
911            "codebase_cite" => self.tool_codebase_cite(id.clone(), &arguments),
912            // 5. Hallucination Detector
913            "hallucination_check" => self.tool_hallucination_check(id.clone(), &arguments),
914            // 6. Truth Maintenance
915            "truth_register" => self.tool_truth_register(id.clone(), &arguments),
916            "truth_check" => self.tool_truth_check(id.clone(), &arguments),
917            // 7. Concept Navigation
918            "concept_find" => self.tool_concept_find(id.clone(), &arguments),
919            "concept_map" => self.tool_concept_map(id.clone(), &arguments),
920            "concept_explain" => self.tool_concept_explain(id.clone(), &arguments),
921            // 8. Architecture Inference
922            "architecture_infer" => self.tool_architecture_infer(id.clone(), &arguments),
923            "architecture_validate" => self.tool_architecture_validate(id.clone(), &arguments),
924            // 9. Semantic Search
925            "search_semantic" => self.tool_search_semantic(id.clone(), &arguments),
926            "search_similar" => self.tool_search_similar(id.clone(), &arguments),
927            "search_explain" => self.tool_search_explain(id.clone(), &arguments),
928            // 10. Multi-Codebase Compare
929            "compare_codebases" => self.tool_compare_codebases(id.clone(), &arguments),
930            "compare_concept" => self.tool_compare_concept(id.clone(), &arguments),
931            "compare_migrate" => self.tool_compare_migrate(id.clone(), &arguments),
932            // 11. Version Archaeology
933            "archaeology_node" => self.tool_archaeology_node(id.clone(), &arguments),
934            "archaeology_why" => self.tool_archaeology_why(id.clone(), &arguments),
935            "archaeology_when" => self.tool_archaeology_when(id.clone(), &arguments),
936            // 12. Pattern Extraction
937            "pattern_extract" => self.tool_pattern_extract(id.clone(), &arguments),
938            "pattern_check" => self.tool_pattern_check(id.clone(), &arguments),
939            "pattern_suggest" => self.tool_pattern_suggest(id.clone(), &arguments),
940            // 13. Code Resurrection
941            "resurrect_search" => self.tool_resurrect_search(id.clone(), &arguments),
942            "resurrect_attempt" => self.tool_resurrect_attempt(id.clone(), &arguments),
943            "resurrect_verify" => self.tool_resurrect_verify(id.clone(), &arguments),
944            "resurrect_history" => self.tool_resurrect_history(id.clone(), &arguments),
945            // 14. Code Genetics
946            "genetics_dna" => self.tool_genetics_dna(id.clone(), &arguments),
947            "genetics_lineage" => self.tool_genetics_lineage(id.clone(), &arguments),
948            "genetics_mutations" => self.tool_genetics_mutations(id.clone(), &arguments),
949            "genetics_diseases" => self.tool_genetics_diseases(id.clone(), &arguments),
950            // 15. Code Telepathy
951            "telepathy_connect" => self.tool_telepathy_connect(id.clone(), &arguments),
952            "telepathy_broadcast" => self.tool_telepathy_broadcast(id.clone(), &arguments),
953            "telepathy_listen" => self.tool_telepathy_listen(id.clone(), &arguments),
954            "telepathy_consensus" => self.tool_telepathy_consensus(id.clone(), &arguments),
955            // 16. Code Soul
956            "soul_extract" => self.tool_soul_extract(id.clone(), &arguments),
957            "soul_compare" => self.tool_soul_compare(id.clone(), &arguments),
958            "soul_preserve" => self.tool_soul_preserve(id.clone(), &arguments),
959            "soul_reincarnate" => self.tool_soul_reincarnate(id.clone(), &arguments),
960            "soul_karma" => self.tool_soul_karma(id.clone(), &arguments),
961            // 17. Code Omniscience
962            "omniscience_search" => self.tool_omniscience_search(id.clone(), &arguments),
963            "omniscience_best" => self.tool_omniscience_best(id.clone(), &arguments),
964            "omniscience_census" => self.tool_omniscience_census(id.clone(), &arguments),
965            "omniscience_vuln" => self.tool_omniscience_vuln(id.clone(), &arguments),
966            "omniscience_trend" => self.tool_omniscience_trend(id.clone(), &arguments),
967            "omniscience_compare" => self.tool_omniscience_compare(id.clone(), &arguments),
968            "omniscience_api_usage" => self.tool_omniscience_api_usage(id.clone(), &arguments),
969            "omniscience_solve" => self.tool_omniscience_solve(id.clone(), &arguments),
970            _ => {
971                return JsonRpcResponse::error(
972                    id,
973                    JsonRpcError::tool_not_found(format!("Tool not found: {}", tool_name)),
974                );
975            }
976        };
977
978        // Per MCP spec: tool execution errors use isError: true, not JSON-RPC errors.
979        // Protocol errors (tool not found, parse error) stay as JSON-RPC errors.
980        let result = result.into_tool_error_if_needed();
981
982        // Auto-log the tool call (skip analysis_log to avoid recursion).
983        let now = std::time::SystemTime::now()
984            .duration_since(std::time::UNIX_EPOCH)
985            .unwrap_or_default()
986            .as_secs();
987        let summary = truncate_json_summary(&arguments, 200);
988        let graph_name = arguments
989            .get("graph")
990            .and_then(|v| v.as_str())
991            .map(String::from);
992        self.operation_log.push(OperationRecord {
993            tool_name: tool_name.to_string(),
994            summary,
995            timestamp: now,
996            graph_name,
997        });
998
999        result
1000    }
1001
1002    /// Handle "resources/list".
1003    fn handle_resources_list(&self, id: Value) -> JsonRpcResponse {
1004        let mut resources = Vec::new();
1005
1006        for name in self.graphs.keys() {
1007            resources.push(json!({
1008                "uri": format!("acb://graphs/{}/stats", name),
1009                "name": format!("{} statistics", name),
1010                "description": format!("Statistics for the {} code graph", name),
1011                "mimeType": "application/json"
1012            }));
1013            resources.push(json!({
1014                "uri": format!("acb://graphs/{}/units", name),
1015                "name": format!("{} units", name),
1016                "description": format!("All code units in the {} graph", name),
1017                "mimeType": "application/json"
1018            }));
1019        }
1020
1021        JsonRpcResponse::success(id, json!({ "resources": resources }))
1022    }
1023
1024    /// Handle "resources/read".
1025    fn handle_resources_read(&self, id: Value, params: &Value) -> JsonRpcResponse {
1026        let uri = match params.get("uri").and_then(|v| v.as_str()) {
1027            Some(u) => u,
1028            None => {
1029                return JsonRpcResponse::error(
1030                    id,
1031                    JsonRpcError::invalid_params("Missing 'uri' field"),
1032                );
1033            }
1034        };
1035
1036        // Parse URI: acb://graphs/{name}/stats or acb://graphs/{name}/units
1037        if let Some(rest) = uri.strip_prefix("acb://graphs/") {
1038            let parts: Vec<&str> = rest.splitn(2, '/').collect();
1039            if parts.len() == 2 {
1040                let graph_name = parts[0];
1041                let resource = parts[1];
1042
1043                if let Some(graph) = self.graphs.get(graph_name) {
1044                    return match resource {
1045                        "stats" => {
1046                            let stats = graph.stats();
1047                            JsonRpcResponse::success(
1048                                id,
1049                                json!({
1050                                    "contents": [{
1051                                        "uri": uri,
1052                                        "mimeType": "application/json",
1053                                        "text": serde_json::to_string_pretty(&json!({
1054                                            "unit_count": stats.unit_count,
1055                                            "edge_count": stats.edge_count,
1056                                            "dimension": stats.dimension,
1057                                        })).unwrap_or_default()
1058                                    }]
1059                                }),
1060                            )
1061                        }
1062                        "units" => {
1063                            let units: Vec<Value> = graph
1064                                .units()
1065                                .iter()
1066                                .map(|u| {
1067                                    json!({
1068                                        "id": u.id,
1069                                        "name": u.name,
1070                                        "type": u.unit_type.label(),
1071                                        "file": u.file_path.display().to_string(),
1072                                    })
1073                                })
1074                                .collect();
1075                            JsonRpcResponse::success(
1076                                id,
1077                                json!({
1078                                    "contents": [{
1079                                        "uri": uri,
1080                                        "mimeType": "application/json",
1081                                        "text": serde_json::to_string_pretty(&units).unwrap_or_default()
1082                                    }]
1083                                }),
1084                            )
1085                        }
1086                        _ => JsonRpcResponse::error(
1087                            id,
1088                            JsonRpcError::invalid_params(format!(
1089                                "Unknown resource type: {}",
1090                                resource
1091                            )),
1092                        ),
1093                    };
1094                } else {
1095                    return JsonRpcResponse::error(
1096                        id,
1097                        JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)),
1098                    );
1099                }
1100            }
1101        }
1102
1103        JsonRpcResponse::error(
1104            id,
1105            JsonRpcError::invalid_params(format!("Invalid resource URI: {}", uri)),
1106        )
1107    }
1108
1109    /// Handle "prompts/list".
1110    fn handle_prompts_list(&self, id: Value) -> JsonRpcResponse {
1111        JsonRpcResponse::success(
1112            id,
1113            json!({
1114                "prompts": [
1115                    {
1116                        "name": "analyse_unit",
1117                        "description": "Analyse a code unit including its dependencies, stability, and test coverage",
1118                        "arguments": [
1119                            {
1120                                "name": "graph",
1121                                "description": "Graph name",
1122                                "required": false
1123                            },
1124                            {
1125                                "name": "unit_name",
1126                                "description": "Name of the code unit to analyse",
1127                                "required": true
1128                            }
1129                        ]
1130                    },
1131                    {
1132                        "name": "explain_coupling",
1133                        "description": "Explain coupling between two code units",
1134                        "arguments": [
1135                            {
1136                                "name": "graph",
1137                                "description": "Graph name",
1138                                "required": false
1139                            },
1140                            {
1141                                "name": "unit_a",
1142                                "description": "First unit name",
1143                                "required": true
1144                            },
1145                            {
1146                                "name": "unit_b",
1147                                "description": "Second unit name",
1148                                "required": true
1149                            }
1150                        ]
1151                    }
1152                ]
1153            }),
1154        )
1155    }
1156
1157    // ========================================================================
1158    // Tool implementations
1159    // ========================================================================
1160
1161    /// Resolve a graph name from arguments, defaulting to the first loaded graph.
1162    fn resolve_graph<'a>(
1163        &'a self,
1164        args: &'a Value,
1165    ) -> Result<(&'a str, &'a CodeGraph), JsonRpcError> {
1166        let graph_name = args.get("graph").and_then(|v| v.as_str()).unwrap_or("");
1167
1168        if graph_name.is_empty() {
1169            // Use the first graph if available.
1170            if let Some((name, graph)) = self.graphs.iter().next() {
1171                return Ok((name.as_str(), graph));
1172            }
1173            return Err(JsonRpcError::invalid_params(
1174                "No graphs loaded. Start the MCP server with --graph <path.acb>, \
1175                 or set AGENTRA_WORKSPACE_ROOT to a repository for auto-compilation.",
1176            ));
1177        }
1178
1179        self.graphs
1180            .get(graph_name)
1181            .map(|g| (graph_name, g))
1182            .ok_or_else(|| JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)))
1183    }
1184
1185    /// Tool: symbol_lookup.
1186    fn tool_symbol_lookup(&self, id: Value, args: &Value) -> JsonRpcResponse {
1187        let (_, graph) = match self.resolve_graph(args) {
1188            Ok(g) => g,
1189            Err(e) => return JsonRpcResponse::error(id, e),
1190        };
1191
1192        let name = match args.get("name").and_then(|v| v.as_str()) {
1193            Some(n) => n.to_string(),
1194            None => {
1195                return JsonRpcResponse::error(
1196                    id,
1197                    JsonRpcError::invalid_params("Missing 'name' argument"),
1198                );
1199            }
1200        };
1201
1202        let mode_raw = args
1203            .get("mode")
1204            .and_then(|v| v.as_str())
1205            .unwrap_or("prefix");
1206        let mode = match mode_raw {
1207            "exact" => MatchMode::Exact,
1208            "prefix" => MatchMode::Prefix,
1209            "contains" => MatchMode::Contains,
1210            "fuzzy" => MatchMode::Fuzzy,
1211            _ => {
1212                return JsonRpcResponse::error(
1213                    id,
1214                    JsonRpcError::invalid_params(format!(
1215                        "Invalid 'mode': {mode_raw}. Expected one of: exact, prefix, contains, fuzzy"
1216                    )),
1217                );
1218            }
1219        };
1220
1221        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1222
1223        let params = SymbolLookupParams {
1224            name,
1225            mode,
1226            limit,
1227            ..SymbolLookupParams::default()
1228        };
1229
1230        match self.engine.symbol_lookup(graph, params) {
1231            Ok(units) => {
1232                let results: Vec<Value> = units
1233                    .iter()
1234                    .map(|u| {
1235                        json!({
1236                            "id": u.id,
1237                            "name": u.name,
1238                            "qualified_name": u.qualified_name,
1239                            "type": u.unit_type.label(),
1240                            "file": u.file_path.display().to_string(),
1241                            "language": u.language.name(),
1242                            "complexity": u.complexity,
1243                        })
1244                    })
1245                    .collect();
1246                JsonRpcResponse::success(
1247                    id,
1248                    json!({
1249                        "content": [{
1250                            "type": "text",
1251                            "text": serde_json::to_string_pretty(&results).unwrap_or_default()
1252                        }]
1253                    }),
1254                )
1255            }
1256            Err(e) => JsonRpcResponse::error(id, JsonRpcError::internal_error(e.to_string())),
1257        }
1258    }
1259
1260    /// Tool: impact_analysis.
1261    fn tool_impact_analysis(&self, id: Value, args: &Value) -> JsonRpcResponse {
1262        let (_, graph) = match self.resolve_graph(args) {
1263            Ok(g) => g,
1264            Err(e) => return JsonRpcResponse::error(id, e),
1265        };
1266
1267        let unit_id = match args.get("unit_id").and_then(|v| v.as_u64()) {
1268            Some(uid) => uid,
1269            None => {
1270                return JsonRpcResponse::error(
1271                    id,
1272                    JsonRpcError::invalid_params("Missing 'unit_id' argument"),
1273                );
1274            }
1275        };
1276
1277        let max_depth = match args.get("max_depth") {
1278            None => 3,
1279            Some(v) => {
1280                let depth = match v.as_i64() {
1281                    Some(d) => d,
1282                    None => {
1283                        return JsonRpcResponse::error(
1284                            id,
1285                            JsonRpcError::invalid_params("'max_depth' must be an integer >= 0"),
1286                        );
1287                    }
1288                };
1289                if depth < 0 {
1290                    return JsonRpcResponse::error(
1291                        id,
1292                        JsonRpcError::invalid_params("'max_depth' must be >= 0"),
1293                    );
1294                }
1295                depth as u32
1296            }
1297        };
1298        let edge_types = vec![
1299            EdgeType::Calls,
1300            EdgeType::Imports,
1301            EdgeType::Inherits,
1302            EdgeType::Implements,
1303            EdgeType::UsesType,
1304            EdgeType::FfiBinds,
1305            EdgeType::References,
1306            EdgeType::Returns,
1307            EdgeType::ParamType,
1308            EdgeType::Overrides,
1309            EdgeType::Contains,
1310        ];
1311
1312        let params = ImpactParams {
1313            unit_id,
1314            max_depth,
1315            edge_types,
1316        };
1317
1318        match self.engine.impact_analysis(graph, params) {
1319            Ok(result) => {
1320                let impacted: Vec<Value> = result
1321                    .impacted
1322                    .iter()
1323                    .map(|i| {
1324                        json!({
1325                            "unit_id": i.unit_id,
1326                            "depth": i.depth,
1327                            "risk_score": i.risk_score,
1328                            "has_tests": i.has_tests,
1329                        })
1330                    })
1331                    .collect();
1332                JsonRpcResponse::success(
1333                    id,
1334                    json!({
1335                        "content": [{
1336                            "type": "text",
1337                            "text": serde_json::to_string_pretty(&json!({
1338                                "root_id": result.root_id,
1339                                "overall_risk": result.overall_risk,
1340                                "impacted_count": result.impacted.len(),
1341                                "impacted": impacted,
1342                                "recommendations": result.recommendations,
1343                            })).unwrap_or_default()
1344                        }]
1345                    }),
1346                )
1347            }
1348            Err(e) => JsonRpcResponse::error(id, JsonRpcError::internal_error(e.to_string())),
1349        }
1350    }
1351
1352    /// Tool: graph_stats.
1353    fn tool_graph_stats(&self, id: Value, args: &Value) -> JsonRpcResponse {
1354        let (name, graph) = match self.resolve_graph(args) {
1355            Ok(g) => g,
1356            Err(e) => return JsonRpcResponse::error(id, e),
1357        };
1358
1359        let stats = graph.stats();
1360        JsonRpcResponse::success(
1361            id,
1362            json!({
1363                "content": [{
1364                    "type": "text",
1365                    "text": serde_json::to_string_pretty(&json!({
1366                        "graph": name,
1367                        "unit_count": stats.unit_count,
1368                        "edge_count": stats.edge_count,
1369                        "dimension": stats.dimension,
1370                    })).unwrap_or_default()
1371                }]
1372            }),
1373        )
1374    }
1375
1376    /// Tool: list_units.
1377    fn tool_list_units(&self, id: Value, args: &Value) -> JsonRpcResponse {
1378        let (_, graph) = match self.resolve_graph(args) {
1379            Ok(g) => g,
1380            Err(e) => return JsonRpcResponse::error(id, e),
1381        };
1382
1383        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(50) as usize;
1384        let unit_type_filter = match args.get("unit_type").and_then(|v| v.as_str()) {
1385            Some(raw) => match Self::parse_unit_type(raw) {
1386                Some(parsed) => Some(parsed),
1387                None => {
1388                    return JsonRpcResponse::error(
1389                        id,
1390                        JsonRpcError::invalid_params(format!(
1391                            "Unknown unit_type '{}'. Expected one of: module, symbol, type, function, parameter, import, test, doc, config, pattern, trait, impl, macro.",
1392                            raw
1393                        )),
1394                    );
1395                }
1396            },
1397            None => None,
1398        };
1399
1400        let units: Vec<Value> = graph
1401            .units()
1402            .iter()
1403            .filter(|u| {
1404                if let Some(expected) = unit_type_filter {
1405                    u.unit_type == expected
1406                } else {
1407                    true
1408                }
1409            })
1410            .take(limit)
1411            .map(|u| {
1412                json!({
1413                    "id": u.id,
1414                    "name": u.name,
1415                    "type": u.unit_type.label(),
1416                    "file": u.file_path.display().to_string(),
1417                })
1418            })
1419            .collect();
1420
1421        JsonRpcResponse::success(
1422            id,
1423            json!({
1424                "content": [{
1425                    "type": "text",
1426                    "text": serde_json::to_string_pretty(&units).unwrap_or_default()
1427                }]
1428            }),
1429        )
1430    }
1431
1432    /// Tool: analysis_log — record the intent/context behind a code analysis.
1433    fn tool_analysis_log(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1434        let intent = match args.get("intent").and_then(|v| v.as_str()) {
1435            Some(i) if !i.trim().is_empty() => i,
1436            _ => {
1437                return JsonRpcResponse::error(
1438                    id,
1439                    JsonRpcError::invalid_params("'intent' is required and must not be empty"),
1440                );
1441            }
1442        };
1443
1444        let finding = args.get("finding").and_then(|v| v.as_str());
1445        let graph_name = args.get("graph").and_then(|v| v.as_str());
1446        let topic = args.get("topic").and_then(|v| v.as_str());
1447
1448        let now = std::time::SystemTime::now()
1449            .duration_since(std::time::UNIX_EPOCH)
1450            .unwrap_or_default()
1451            .as_secs();
1452
1453        let mut summary_parts = vec![format!("intent: {intent}")];
1454        if let Some(f) = finding {
1455            summary_parts.push(format!("finding: {f}"));
1456        }
1457        if let Some(t) = topic {
1458            summary_parts.push(format!("topic: {t}"));
1459        }
1460
1461        let record = OperationRecord {
1462            tool_name: "analysis_log".to_string(),
1463            summary: summary_parts.join(" | "),
1464            timestamp: now,
1465            graph_name: graph_name.map(String::from),
1466        };
1467
1468        let index = self.operation_log.len();
1469        self.operation_log.push(record);
1470
1471        JsonRpcResponse::success(
1472            id,
1473            json!({
1474                "content": [{
1475                    "type": "text",
1476                    "text": serde_json::to_string_pretty(&json!({
1477                        "log_index": index,
1478                        "message": "Analysis context logged"
1479                    })).unwrap_or_default()
1480                }]
1481            }),
1482        )
1483    }
1484
1485    /// Access the operation log.
1486    pub fn operation_log(&self) -> &[OperationRecord] {
1487        &self.operation_log
1488    }
1489
1490    /// Access the workspace manager.
1491    pub fn workspace_manager(&self) -> &WorkspaceManager {
1492        &self.workspace_manager
1493    }
1494
1495    /// Access the workspace manager mutably.
1496    pub fn workspace_manager_mut(&mut self) -> &mut WorkspaceManager {
1497        &mut self.workspace_manager
1498    }
1499
1500    // ========================================================================
1501    // Grounding tool implementations
1502    // ========================================================================
1503
1504    /// Tool: codebase_ground — verify a claim about code has graph evidence.
1505    fn tool_codebase_ground(&self, id: Value, args: &Value) -> JsonRpcResponse {
1506        let (_, graph) = match self.resolve_graph(args) {
1507            Ok(g) => g,
1508            Err(e) => return JsonRpcResponse::error(id, e),
1509        };
1510
1511        let claim = match args.get("claim").and_then(|v| v.as_str()) {
1512            Some(c) if !c.trim().is_empty() => c,
1513            _ => {
1514                return JsonRpcResponse::error(
1515                    id,
1516                    JsonRpcError::invalid_params("Missing or empty 'claim' argument"),
1517                );
1518            }
1519        };
1520
1521        let strict = args
1522            .get("strict")
1523            .and_then(|v| v.as_bool())
1524            .unwrap_or(false);
1525
1526        let engine = GroundingEngine::new(graph);
1527        let result = engine.ground_claim(claim);
1528
1529        // In strict mode, Partial is treated as Ungrounded.
1530        let result = if strict {
1531            match result {
1532                GroundingResult::Partial {
1533                    unsupported,
1534                    suggestions,
1535                    ..
1536                } => GroundingResult::Ungrounded {
1537                    claim: claim.to_string(),
1538                    suggestions: {
1539                        let mut s = unsupported;
1540                        s.extend(suggestions);
1541                        s
1542                    },
1543                },
1544                other => other,
1545            }
1546        } else {
1547            result
1548        };
1549
1550        let output = match &result {
1551            GroundingResult::Verified {
1552                evidence,
1553                confidence,
1554            } => json!({
1555                "status": "verified",
1556                "confidence": confidence,
1557                "evidence": evidence.iter().map(|e| json!({
1558                    "node_id": e.node_id,
1559                    "node_type": e.node_type,
1560                    "name": e.name,
1561                    "file_path": e.file_path,
1562                    "line_number": e.line_number,
1563                    "snippet": e.snippet,
1564                })).collect::<Vec<_>>(),
1565            }),
1566            GroundingResult::Partial {
1567                supported,
1568                unsupported,
1569                suggestions,
1570            } => json!({
1571                "status": "partial",
1572                "supported": supported,
1573                "unsupported": unsupported,
1574                "suggestions": suggestions,
1575            }),
1576            GroundingResult::Ungrounded {
1577                claim, suggestions, ..
1578            } => json!({
1579                "status": "ungrounded",
1580                "claim": claim,
1581                "suggestions": suggestions,
1582            }),
1583        };
1584
1585        JsonRpcResponse::success(
1586            id,
1587            json!({
1588                "content": [{
1589                    "type": "text",
1590                    "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1591                }]
1592            }),
1593        )
1594    }
1595
1596    /// Tool: codebase_evidence — get graph evidence for a symbol name.
1597    fn tool_codebase_evidence(&self, id: Value, args: &Value) -> JsonRpcResponse {
1598        let (_, graph) = match self.resolve_graph(args) {
1599            Ok(g) => g,
1600            Err(e) => return JsonRpcResponse::error(id, e),
1601        };
1602
1603        let name = match args.get("name").and_then(|v| v.as_str()) {
1604            Some(n) if !n.trim().is_empty() => n,
1605            _ => {
1606                return JsonRpcResponse::error(
1607                    id,
1608                    JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1609                );
1610            }
1611        };
1612
1613        let type_filters: Vec<String> = args
1614            .get("types")
1615            .and_then(|v| v.as_array())
1616            .map(|arr| {
1617                arr.iter()
1618                    .filter_map(|v| v.as_str().map(|s| s.to_lowercase()))
1619                    .collect()
1620            })
1621            .unwrap_or_default();
1622
1623        let engine = GroundingEngine::new(graph);
1624        let mut evidence = engine.find_evidence(name);
1625
1626        // Apply type filters if provided.
1627        if !type_filters.is_empty() {
1628            evidence.retain(|e| type_filters.contains(&e.node_type.to_lowercase()));
1629        }
1630
1631        let output: Vec<Value> = evidence
1632            .iter()
1633            .map(|e| {
1634                json!({
1635                    "node_id": e.node_id,
1636                    "node_type": e.node_type,
1637                    "name": e.name,
1638                    "file_path": e.file_path,
1639                    "line_number": e.line_number,
1640                    "snippet": e.snippet,
1641                })
1642            })
1643            .collect();
1644
1645        JsonRpcResponse::success(
1646            id,
1647            json!({
1648                "content": [{
1649                    "type": "text",
1650                    "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1651                }]
1652            }),
1653        )
1654    }
1655
1656    /// Tool: codebase_suggest — find symbols similar to a name.
1657    fn tool_codebase_suggest(&self, id: Value, args: &Value) -> JsonRpcResponse {
1658        let (_, graph) = match self.resolve_graph(args) {
1659            Ok(g) => g,
1660            Err(e) => return JsonRpcResponse::error(id, e),
1661        };
1662
1663        let name = match args.get("name").and_then(|v| v.as_str()) {
1664            Some(n) if !n.trim().is_empty() => n,
1665            _ => {
1666                return JsonRpcResponse::error(
1667                    id,
1668                    JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1669                );
1670            }
1671        };
1672
1673        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
1674
1675        let engine = GroundingEngine::new(graph);
1676        let suggestions = engine.suggest_similar(name, limit);
1677
1678        JsonRpcResponse::success(
1679            id,
1680            json!({
1681                "content": [{
1682                    "type": "text",
1683                    "text": serde_json::to_string_pretty(&json!({
1684                        "query": name,
1685                        "suggestions": suggestions,
1686                    })).unwrap_or_default()
1687                }]
1688            }),
1689        )
1690    }
1691
1692    // ========================================================================
1693    // Workspace tool implementations
1694    // ========================================================================
1695
1696    /// Resolve a workspace ID from arguments. Accepts workspace ID directly
1697    /// or tries to match by name.
1698    fn resolve_workspace_id(&self, args: &Value) -> Result<String, JsonRpcError> {
1699        let raw = args.get("workspace").and_then(|v| v.as_str()).unwrap_or("");
1700        if raw.is_empty() {
1701            // Try active workspace.
1702            return self
1703                .workspace_manager
1704                .get_active()
1705                .map(|s| s.to_string())
1706                .ok_or_else(|| {
1707                    JsonRpcError::invalid_params("No workspace specified and none active")
1708                });
1709        }
1710
1711        // If it looks like a workspace ID (starts with "ws-"), use directly.
1712        if raw.starts_with("ws-") {
1713            // Validate it exists.
1714            self.workspace_manager
1715                .list(raw)
1716                .map(|_| raw.to_string())
1717                .map_err(JsonRpcError::invalid_params)
1718        } else {
1719            // Try to find by name — iterate all workspaces. We need to expose
1720            // this through the manager. For now, just treat it as an ID.
1721            self.workspace_manager
1722                .list(raw)
1723                .map(|_| raw.to_string())
1724                .map_err(JsonRpcError::invalid_params)
1725        }
1726    }
1727
1728    /// Tool: workspace_create.
1729    fn tool_workspace_create(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1730        let name = match args.get("name").and_then(|v| v.as_str()) {
1731            Some(n) if !n.trim().is_empty() => n,
1732            _ => {
1733                return JsonRpcResponse::error(
1734                    id,
1735                    JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1736                );
1737            }
1738        };
1739
1740        let ws_id = self.workspace_manager.create(name);
1741
1742        JsonRpcResponse::success(
1743            id,
1744            json!({
1745                "content": [{
1746                    "type": "text",
1747                    "text": serde_json::to_string_pretty(&json!({
1748                        "workspace_id": ws_id,
1749                        "name": name,
1750                        "message": "Workspace created"
1751                    })).unwrap_or_default()
1752                }]
1753            }),
1754        )
1755    }
1756
1757    /// Tool: workspace_add — add a loaded graph as a context.
1758    fn tool_workspace_add(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1759        let ws_id = match self.resolve_workspace_id(args) {
1760            Ok(ws) => ws,
1761            Err(e) => return JsonRpcResponse::error(id, e),
1762        };
1763
1764        let graph_name = match args.get("graph").and_then(|v| v.as_str()) {
1765            Some(n) if !n.trim().is_empty() => n.to_string(),
1766            _ => {
1767                return JsonRpcResponse::error(
1768                    id,
1769                    JsonRpcError::invalid_params("Missing or empty 'graph' argument"),
1770                );
1771            }
1772        };
1773
1774        // Clone the graph to add to workspace (graphs remain available in the server).
1775        let graph = match self.graphs.get(&graph_name) {
1776            Some(g) => g.clone(),
1777            None => {
1778                return JsonRpcResponse::error(
1779                    id,
1780                    JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)),
1781                );
1782            }
1783        };
1784
1785        let role_str = args
1786            .get("role")
1787            .and_then(|v| v.as_str())
1788            .unwrap_or("source");
1789        let role = match ContextRole::parse_str(role_str) {
1790            Some(r) => r,
1791            None => {
1792                return JsonRpcResponse::error(
1793                    id,
1794                    JsonRpcError::invalid_params(format!(
1795                        "Invalid role '{}'. Expected: source, target, reference, comparison",
1796                        role_str
1797                    )),
1798                );
1799            }
1800        };
1801
1802        let path = args
1803            .get("path")
1804            .and_then(|v| v.as_str())
1805            .unwrap_or(&graph_name)
1806            .to_string();
1807        let language = args
1808            .get("language")
1809            .and_then(|v| v.as_str())
1810            .map(String::from);
1811
1812        match self
1813            .workspace_manager
1814            .add_context(&ws_id, &path, role, language, graph)
1815        {
1816            Ok(ctx_id) => JsonRpcResponse::success(
1817                id,
1818                json!({
1819                    "content": [{
1820                        "type": "text",
1821                        "text": serde_json::to_string_pretty(&json!({
1822                            "context_id": ctx_id,
1823                            "workspace_id": ws_id,
1824                            "graph": graph_name,
1825                            "message": "Context added to workspace"
1826                        })).unwrap_or_default()
1827                    }]
1828                }),
1829            ),
1830            Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1831        }
1832    }
1833
1834    /// Tool: workspace_list.
1835    fn tool_workspace_list(&self, id: Value, args: &Value) -> JsonRpcResponse {
1836        let ws_id = match self.resolve_workspace_id(args) {
1837            Ok(ws) => ws,
1838            Err(e) => return JsonRpcResponse::error(id, e),
1839        };
1840
1841        match self.workspace_manager.list(&ws_id) {
1842            Ok(workspace) => {
1843                let contexts: Vec<Value> = workspace
1844                    .contexts
1845                    .iter()
1846                    .map(|c| {
1847                        json!({
1848                            "id": c.id,
1849                            "role": c.role.label(),
1850                            "path": c.path,
1851                            "language": c.language,
1852                            "unit_count": c.graph.units().len(),
1853                        })
1854                    })
1855                    .collect();
1856
1857                JsonRpcResponse::success(
1858                    id,
1859                    json!({
1860                        "content": [{
1861                            "type": "text",
1862                            "text": serde_json::to_string_pretty(&json!({
1863                                "workspace_id": ws_id,
1864                                "name": workspace.name,
1865                                "context_count": workspace.contexts.len(),
1866                                "contexts": contexts,
1867                            })).unwrap_or_default()
1868                        }]
1869                    }),
1870                )
1871            }
1872            Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1873        }
1874    }
1875
1876    /// Tool: workspace_query.
1877    fn tool_workspace_query(&self, id: Value, args: &Value) -> JsonRpcResponse {
1878        let ws_id = match self.resolve_workspace_id(args) {
1879            Ok(ws) => ws,
1880            Err(e) => return JsonRpcResponse::error(id, e),
1881        };
1882
1883        let query = match args.get("query").and_then(|v| v.as_str()) {
1884            Some(q) if !q.trim().is_empty() => q,
1885            _ => {
1886                return JsonRpcResponse::error(
1887                    id,
1888                    JsonRpcError::invalid_params("Missing or empty 'query' argument"),
1889                );
1890            }
1891        };
1892
1893        let role_filters: Vec<String> = args
1894            .get("roles")
1895            .and_then(|v| v.as_array())
1896            .map(|arr| {
1897                arr.iter()
1898                    .filter_map(|v| v.as_str().map(|s| s.to_lowercase()))
1899                    .collect()
1900            })
1901            .unwrap_or_default();
1902
1903        match self.workspace_manager.query_all(&ws_id, query) {
1904            Ok(results) => {
1905                let mut filtered = results;
1906                if !role_filters.is_empty() {
1907                    filtered.retain(|r| role_filters.contains(&r.context_role.label().to_string()));
1908                }
1909
1910                let output: Vec<Value> = filtered
1911                    .iter()
1912                    .map(|r| {
1913                        json!({
1914                            "context_id": r.context_id,
1915                            "role": r.context_role.label(),
1916                            "matches": r.matches.iter().map(|m| json!({
1917                                "unit_id": m.unit_id,
1918                                "name": m.name,
1919                                "qualified_name": m.qualified_name,
1920                                "type": m.unit_type,
1921                                "file": m.file_path,
1922                            })).collect::<Vec<_>>(),
1923                        })
1924                    })
1925                    .collect();
1926
1927                JsonRpcResponse::success(
1928                    id,
1929                    json!({
1930                        "content": [{
1931                            "type": "text",
1932                            "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1933                        }]
1934                    }),
1935                )
1936            }
1937            Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1938        }
1939    }
1940
1941    /// Tool: workspace_compare.
1942    fn tool_workspace_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
1943        let ws_id = match self.resolve_workspace_id(args) {
1944            Ok(ws) => ws,
1945            Err(e) => return JsonRpcResponse::error(id, e),
1946        };
1947
1948        let symbol = match args.get("symbol").and_then(|v| v.as_str()) {
1949            Some(s) if !s.trim().is_empty() => s,
1950            _ => {
1951                return JsonRpcResponse::error(
1952                    id,
1953                    JsonRpcError::invalid_params("Missing or empty 'symbol' argument"),
1954                );
1955            }
1956        };
1957
1958        match self.workspace_manager.compare(&ws_id, symbol) {
1959            Ok(cmp) => {
1960                let contexts: Vec<Value> = cmp
1961                    .contexts
1962                    .iter()
1963                    .map(|c| {
1964                        json!({
1965                            "context_id": c.context_id,
1966                            "role": c.role.label(),
1967                            "found": c.found,
1968                            "unit_type": c.unit_type,
1969                            "signature": c.signature,
1970                            "file_path": c.file_path,
1971                        })
1972                    })
1973                    .collect();
1974
1975                JsonRpcResponse::success(
1976                    id,
1977                    json!({
1978                        "content": [{
1979                            "type": "text",
1980                            "text": serde_json::to_string_pretty(&json!({
1981                                "symbol": cmp.symbol,
1982                                "semantic_match": cmp.semantic_match,
1983                                "structural_diff": cmp.structural_diff,
1984                                "contexts": contexts,
1985                            })).unwrap_or_default()
1986                        }]
1987                    }),
1988                )
1989            }
1990            Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1991        }
1992    }
1993
1994    /// Tool: workspace_xref.
1995    fn tool_workspace_xref(&self, id: Value, args: &Value) -> JsonRpcResponse {
1996        let ws_id = match self.resolve_workspace_id(args) {
1997            Ok(ws) => ws,
1998            Err(e) => return JsonRpcResponse::error(id, e),
1999        };
2000
2001        let symbol = match args.get("symbol").and_then(|v| v.as_str()) {
2002            Some(s) if !s.trim().is_empty() => s,
2003            _ => {
2004                return JsonRpcResponse::error(
2005                    id,
2006                    JsonRpcError::invalid_params("Missing or empty 'symbol' argument"),
2007                );
2008            }
2009        };
2010
2011        match self.workspace_manager.cross_reference(&ws_id, symbol) {
2012            Ok(xref) => {
2013                let found: Vec<Value> = xref
2014                    .found_in
2015                    .iter()
2016                    .map(|(ctx_id, role)| json!({"context_id": ctx_id, "role": role.label()}))
2017                    .collect();
2018                let missing: Vec<Value> = xref
2019                    .missing_from
2020                    .iter()
2021                    .map(|(ctx_id, role)| json!({"context_id": ctx_id, "role": role.label()}))
2022                    .collect();
2023
2024                JsonRpcResponse::success(
2025                    id,
2026                    json!({
2027                        "content": [{
2028                            "type": "text",
2029                            "text": serde_json::to_string_pretty(&json!({
2030                                "symbol": xref.symbol,
2031                                "found_in": found,
2032                                "missing_from": missing,
2033                            })).unwrap_or_default()
2034                        }]
2035                    }),
2036                )
2037            }
2038            Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2039        }
2040    }
2041
2042    // ========================================================================
2043    // Translation tool implementations
2044    // ========================================================================
2045
2046    /// Tool: translation_record.
2047    fn tool_translation_record(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
2048        let ws_id = match self.resolve_workspace_id(args) {
2049            Ok(ws) => ws,
2050            Err(e) => return JsonRpcResponse::error(id, e),
2051        };
2052
2053        let source_symbol = match args.get("source_symbol").and_then(|v| v.as_str()) {
2054            Some(s) if !s.trim().is_empty() => s,
2055            _ => {
2056                return JsonRpcResponse::error(
2057                    id,
2058                    JsonRpcError::invalid_params("Missing or empty 'source_symbol' argument"),
2059                );
2060            }
2061        };
2062
2063        let target_symbol = args.get("target_symbol").and_then(|v| v.as_str());
2064
2065        let status_str = args
2066            .get("status")
2067            .and_then(|v| v.as_str())
2068            .unwrap_or("not_started");
2069        let status = match TranslationStatus::parse_str(status_str) {
2070            Some(s) => s,
2071            None => {
2072                return JsonRpcResponse::error(
2073                    id,
2074                    JsonRpcError::invalid_params(format!(
2075                        "Invalid status '{}'. Expected: not_started, in_progress, ported, verified, skipped",
2076                        status_str
2077                    )),
2078                );
2079            }
2080        };
2081
2082        let notes = args.get("notes").and_then(|v| v.as_str()).map(String::from);
2083
2084        // Get or create translation map for this workspace.
2085        // Use workspace's first source and first target context IDs.
2086        let tmap = self
2087            .translation_maps
2088            .entry(ws_id.clone())
2089            .or_insert_with(|| {
2090                // Find source and target context IDs from the workspace.
2091                let (src, tgt) = if let Ok(ws) = self.workspace_manager.list(&ws_id) {
2092                    let src = ws
2093                        .contexts
2094                        .iter()
2095                        .find(|c| c.role == ContextRole::Source)
2096                        .map(|c| c.id.clone())
2097                        .unwrap_or_default();
2098                    let tgt = ws
2099                        .contexts
2100                        .iter()
2101                        .find(|c| c.role == ContextRole::Target)
2102                        .map(|c| c.id.clone())
2103                        .unwrap_or_default();
2104                    (src, tgt)
2105                } else {
2106                    (String::new(), String::new())
2107                };
2108                TranslationMap::new(src, tgt)
2109            });
2110
2111        tmap.record(source_symbol, target_symbol, status, notes);
2112
2113        JsonRpcResponse::success(
2114            id,
2115            json!({
2116                "content": [{
2117                    "type": "text",
2118                    "text": serde_json::to_string_pretty(&json!({
2119                        "source_symbol": source_symbol,
2120                        "target_symbol": target_symbol,
2121                        "status": status_str,
2122                        "message": "Translation mapping recorded"
2123                    })).unwrap_or_default()
2124                }]
2125            }),
2126        )
2127    }
2128
2129    /// Tool: translation_progress.
2130    fn tool_translation_progress(&self, id: Value, args: &Value) -> JsonRpcResponse {
2131        let ws_id = match self.resolve_workspace_id(args) {
2132            Ok(ws) => ws,
2133            Err(e) => return JsonRpcResponse::error(id, e),
2134        };
2135
2136        let progress = match self.translation_maps.get(&ws_id) {
2137            Some(tmap) => tmap.progress(),
2138            None => {
2139                // No translation map yet — return zeros.
2140                crate::workspace::TranslationProgress {
2141                    total: 0,
2142                    not_started: 0,
2143                    in_progress: 0,
2144                    ported: 0,
2145                    verified: 0,
2146                    skipped: 0,
2147                    percent_complete: 0.0,
2148                }
2149            }
2150        };
2151
2152        JsonRpcResponse::success(
2153            id,
2154            json!({
2155                "content": [{
2156                    "type": "text",
2157                    "text": serde_json::to_string_pretty(&json!({
2158                        "workspace": ws_id,
2159                        "total": progress.total,
2160                        "not_started": progress.not_started,
2161                        "in_progress": progress.in_progress,
2162                        "ported": progress.ported,
2163                        "verified": progress.verified,
2164                        "skipped": progress.skipped,
2165                        "percent_complete": progress.percent_complete,
2166                    })).unwrap_or_default()
2167                }]
2168            }),
2169        )
2170    }
2171
2172    /// Tool: translation_remaining.
2173    fn tool_translation_remaining(&self, id: Value, args: &Value) -> JsonRpcResponse {
2174        let ws_id = match self.resolve_workspace_id(args) {
2175            Ok(ws) => ws,
2176            Err(e) => return JsonRpcResponse::error(id, e),
2177        };
2178
2179        let module_filter = args
2180            .get("module")
2181            .and_then(|v| v.as_str())
2182            .map(|s| s.to_lowercase());
2183
2184        let remaining = match self.translation_maps.get(&ws_id) {
2185            Some(tmap) => {
2186                let mut items = tmap.remaining();
2187                if let Some(ref module) = module_filter {
2188                    items.retain(|m| m.source_symbol.to_lowercase().contains(module.as_str()));
2189                }
2190                items
2191                    .iter()
2192                    .map(|m| {
2193                        json!({
2194                            "source_symbol": m.source_symbol,
2195                            "status": m.status.label(),
2196                            "notes": m.notes,
2197                        })
2198                    })
2199                    .collect::<Vec<_>>()
2200            }
2201            None => Vec::new(),
2202        };
2203
2204        JsonRpcResponse::success(
2205            id,
2206            json!({
2207                "content": [{
2208                    "type": "text",
2209                    "text": serde_json::to_string_pretty(&json!({
2210                        "workspace": ws_id,
2211                        "remaining_count": remaining.len(),
2212                        "remaining": remaining,
2213                    })).unwrap_or_default()
2214                }]
2215            }),
2216        )
2217    }
2218
2219    // ========================================================================
2220    // Invention tool handlers
2221    // ========================================================================
2222
2223    // ── 1. Enhanced Impact Analysis ─────────────────────────────────────
2224
2225    fn tool_impact_analyze(&self, id: Value, args: &Value) -> JsonRpcResponse {
2226        let (_, graph) = match self.resolve_graph(args) {
2227            Ok(g) => g,
2228            Err(e) => return JsonRpcResponse::error(id, e),
2229        };
2230        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2231        let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(5) as u32;
2232        let change_type_str = args
2233            .get("change_type")
2234            .and_then(|v| v.as_str())
2235            .unwrap_or("behavior");
2236        let change_type = match change_type_str {
2237            "signature" => crate::engine::impact::ChangeType::Signature,
2238            "deletion" => crate::engine::impact::ChangeType::Deletion,
2239            "rename" => crate::engine::impact::ChangeType::Rename,
2240            "move" => crate::engine::impact::ChangeType::Move,
2241            _ => crate::engine::impact::ChangeType::Behavior,
2242        };
2243
2244        let analyzer = crate::engine::impact::ImpactAnalyzer::new(graph);
2245        let change = crate::engine::impact::ProposedChange {
2246            target: unit_id,
2247            change_type,
2248            description: format!("Proposed {} change to unit {}", change_type_str, unit_id),
2249        };
2250        let result = analyzer.analyze(change, max_depth);
2251        let viz = analyzer.visualize(&result);
2252
2253        JsonRpcResponse::success(
2254            id,
2255            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&viz).unwrap_or_default() }] }),
2256        )
2257    }
2258
2259    fn tool_impact_path(&self, id: Value, args: &Value) -> JsonRpcResponse {
2260        let (_, graph) = match self.resolve_graph(args) {
2261            Ok(g) => g,
2262            Err(e) => return JsonRpcResponse::error(id, e),
2263        };
2264        let from = args.get("from").and_then(|v| v.as_u64()).unwrap_or(0);
2265        let to = args.get("to").and_then(|v| v.as_u64()).unwrap_or(0);
2266
2267        let analyzer = crate::engine::impact::ImpactAnalyzer::new(graph);
2268        let path = analyzer.impact_path(from, to);
2269
2270        JsonRpcResponse::success(
2271            id,
2272            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "from": from, "to": to, "path": path })).unwrap_or_default() }] }),
2273        )
2274    }
2275
2276    // ── 2. Enhanced Code Prophecy ───────────────────────────────────────
2277
2278    fn tool_prophecy(&self, id: Value, args: &Value) -> JsonRpcResponse {
2279        let (_, graph) = match self.resolve_graph(args) {
2280            Ok(g) => g,
2281            Err(e) => return JsonRpcResponse::error(id, e),
2282        };
2283        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2284
2285        let engine = crate::temporal::prophecy_v2::EnhancedProphecyEngine::new(graph);
2286        let subject = crate::temporal::prophecy_v2::ProphecySubject::Node(unit_id);
2287        let horizon = crate::temporal::prophecy_v2::ProphecyHorizon::MediumTerm;
2288        let result = engine.prophecy(subject, horizon);
2289
2290        JsonRpcResponse::success(
2291            id,
2292            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2293        )
2294    }
2295
2296    fn tool_prophecy_if(&self, id: Value, args: &Value) -> JsonRpcResponse {
2297        let (_, graph) = match self.resolve_graph(args) {
2298            Ok(g) => g,
2299            Err(e) => return JsonRpcResponse::error(id, e),
2300        };
2301        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2302        let change_type_str = args
2303            .get("change_type")
2304            .and_then(|v| v.as_str())
2305            .unwrap_or("behavior");
2306
2307        let engine = crate::temporal::prophecy_v2::EnhancedProphecyEngine::new(graph);
2308        let subject = crate::temporal::prophecy_v2::ProphecySubject::Node(unit_id);
2309        let horizon = crate::temporal::prophecy_v2::ProphecyHorizon::MediumTerm;
2310        let scenario = format!("{} change to unit {}", change_type_str, unit_id);
2311        let result = engine.prophecy_if(subject, &scenario, horizon);
2312
2313        JsonRpcResponse::success(
2314            id,
2315            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2316        )
2317    }
2318
2319    // ── 3. Regression Oracle ────────────────────────────────────────────
2320
2321    fn tool_regression_predict(&self, id: Value, args: &Value) -> JsonRpcResponse {
2322        let (_, graph) = match self.resolve_graph(args) {
2323            Ok(g) => g,
2324            Err(e) => return JsonRpcResponse::error(id, e),
2325        };
2326        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2327        let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(5) as u32;
2328
2329        let predictor = crate::engine::regression::RegressionPredictor::new(graph);
2330        let oracle = predictor.predict(unit_id, max_depth);
2331
2332        let results: Vec<Value> = oracle
2333            .likely_failures
2334            .iter()
2335            .map(|p| {
2336                json!({
2337                    "test_id": p.test.unit_id,
2338                    "test_function": p.test.function,
2339                    "test_file": p.test.file,
2340                    "failure_probability": p.failure_probability,
2341                    "reason": p.reason,
2342                })
2343            })
2344            .collect();
2345
2346        JsonRpcResponse::success(
2347            id,
2348            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "changed_unit": unit_id, "likely_failures": results, "safe_to_skip": oracle.safe_to_skip.len() })).unwrap_or_default() }] }),
2349        )
2350    }
2351
2352    fn tool_regression_minimal(&self, id: Value, args: &Value) -> JsonRpcResponse {
2353        let (_, graph) = match self.resolve_graph(args) {
2354            Ok(g) => g,
2355            Err(e) => return JsonRpcResponse::error(id, e),
2356        };
2357        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2358        let _threshold = args
2359            .get("threshold")
2360            .and_then(|v| v.as_f64())
2361            .unwrap_or(0.5);
2362
2363        let predictor = crate::engine::regression::RegressionPredictor::new(graph);
2364        let minimal = predictor.minimal_test_set(unit_id);
2365
2366        let results: Vec<Value> = minimal
2367            .iter()
2368            .map(|t| {
2369                json!({
2370                    "test_id": t.unit_id,
2371                    "test_function": t.function,
2372                    "test_file": t.file,
2373                })
2374            })
2375            .collect();
2376
2377        JsonRpcResponse::success(
2378            id,
2379            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "changed_unit": unit_id, "minimal_tests": results })).unwrap_or_default() }] }),
2380        )
2381    }
2382
2383    // ── 4. Citation Engine ──────────────────────────────────────────────
2384
2385    fn tool_codebase_ground_claim(&self, id: Value, args: &Value) -> JsonRpcResponse {
2386        let (_, graph) = match self.resolve_graph(args) {
2387            Ok(g) => g,
2388            Err(e) => return JsonRpcResponse::error(id, e),
2389        };
2390        let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2391
2392        let engine = crate::grounding::citation::CitationEngine::new(graph);
2393        let result = engine.ground_claim(claim);
2394
2395        JsonRpcResponse::success(
2396            id,
2397            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2398        )
2399    }
2400
2401    fn tool_codebase_cite(&self, id: Value, args: &Value) -> JsonRpcResponse {
2402        let (_, graph) = match self.resolve_graph(args) {
2403            Ok(g) => g,
2404            Err(e) => return JsonRpcResponse::error(id, e),
2405        };
2406        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2407
2408        let engine = crate::grounding::citation::CitationEngine::new(graph);
2409        let citation = engine.cite_node(unit_id);
2410
2411        JsonRpcResponse::success(
2412            id,
2413            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "citation": citation })).unwrap_or_default() }] }),
2414        )
2415    }
2416
2417    // ── 5. Hallucination Detector ───────────────────────────────────────
2418
2419    fn tool_hallucination_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2420        let (_, graph) = match self.resolve_graph(args) {
2421            Ok(g) => g,
2422            Err(e) => return JsonRpcResponse::error(id, e),
2423        };
2424        let output = args.get("output").and_then(|v| v.as_str()).unwrap_or("");
2425
2426        let detector = crate::grounding::hallucination::HallucinationDetector::new(graph);
2427        let result = detector.check_output(output);
2428
2429        JsonRpcResponse::success(
2430            id,
2431            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2432        )
2433    }
2434
2435    // ── 6. Truth Maintenance ────────────────────────────────────────────
2436
2437    fn tool_truth_register(&self, id: Value, args: &Value) -> JsonRpcResponse {
2438        let (_, graph) = match self.resolve_graph(args) {
2439            Ok(g) => g,
2440            Err(e) => return JsonRpcResponse::error(id, e),
2441        };
2442        let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2443
2444        let mut maintainer = crate::grounding::truth::TruthMaintainer::new(graph);
2445        let truth = maintainer.register_truth(claim);
2446
2447        JsonRpcResponse::success(
2448            id,
2449            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&truth).unwrap_or_default() }] }),
2450        )
2451    }
2452
2453    fn tool_truth_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2454        let (_, graph) = match self.resolve_graph(args) {
2455            Ok(g) => g,
2456            Err(e) => return JsonRpcResponse::error(id, e),
2457        };
2458        let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2459
2460        let maintainer = crate::grounding::truth::TruthMaintainer::new(graph);
2461        let result = maintainer.check_truth(claim);
2462
2463        JsonRpcResponse::success(
2464            id,
2465            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "claim": claim, "status": format!("{:?}", result) })).unwrap_or_default() }] }),
2466        )
2467    }
2468
2469    // ── 7. Concept Navigation ───────────────────────────────────────────
2470
2471    fn tool_concept_find(&self, id: Value, args: &Value) -> JsonRpcResponse {
2472        let (_, graph) = match self.resolve_graph(args) {
2473            Ok(g) => g,
2474            Err(e) => return JsonRpcResponse::error(id, e),
2475        };
2476        let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2477
2478        let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2479        let query = crate::semantic::concept_nav::ConceptQuery {
2480            description: concept.to_string(),
2481            constraints: Vec::new(),
2482        };
2483        let result = navigator.find_concept(query);
2484
2485        JsonRpcResponse::success(
2486            id,
2487            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2488        )
2489    }
2490
2491    fn tool_concept_map(&self, id: Value, args: &Value) -> JsonRpcResponse {
2492        let (_, graph) = match self.resolve_graph(args) {
2493            Ok(g) => g,
2494            Err(e) => return JsonRpcResponse::error(id, e),
2495        };
2496
2497        let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2498        let concepts = navigator.map_all_concepts();
2499
2500        let results: Vec<Value> = concepts
2501            .iter()
2502            .map(|c| {
2503                json!({
2504                    "name": c.name,
2505                    "description": c.description,
2506                    "implementation_count": c.implementations.len(),
2507                })
2508            })
2509            .collect();
2510
2511        JsonRpcResponse::success(
2512            id,
2513            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "concepts": results })).unwrap_or_default() }] }),
2514        )
2515    }
2516
2517    fn tool_concept_explain(&self, id: Value, args: &Value) -> JsonRpcResponse {
2518        let (_, graph) = match self.resolve_graph(args) {
2519            Ok(g) => g,
2520            Err(e) => return JsonRpcResponse::error(id, e),
2521        };
2522        let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2523
2524        let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2525        let result = navigator.explain_concept(concept);
2526
2527        JsonRpcResponse::success(
2528            id,
2529            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2530        )
2531    }
2532
2533    // ── 8. Architecture Inference ───────────────────────────────────────
2534
2535    fn tool_architecture_infer(&self, id: Value, args: &Value) -> JsonRpcResponse {
2536        let (_, graph) = match self.resolve_graph(args) {
2537            Ok(g) => g,
2538            Err(e) => return JsonRpcResponse::error(id, e),
2539        };
2540
2541        let inferrer = crate::semantic::architecture::ArchitectureInferrer::new(graph);
2542        let architecture = inferrer.infer();
2543        let diagram = inferrer.diagram(&architecture);
2544
2545        JsonRpcResponse::success(
2546            id,
2547            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "architecture": architecture, "diagram": diagram })).unwrap_or_default() }] }),
2548        )
2549    }
2550
2551    fn tool_architecture_validate(&self, id: Value, args: &Value) -> JsonRpcResponse {
2552        let (_, graph) = match self.resolve_graph(args) {
2553            Ok(g) => g,
2554            Err(e) => return JsonRpcResponse::error(id, e),
2555        };
2556
2557        let inferrer = crate::semantic::architecture::ArchitectureInferrer::new(graph);
2558        let architecture = inferrer.infer();
2559        let anomalies = inferrer.validate(architecture.pattern);
2560
2561        let results: Vec<Value> = anomalies
2562            .iter()
2563            .map(|a| {
2564                json!({
2565                    "description": a.description,
2566                    "severity": format!("{:?}", a.severity),
2567                    "expected": a.expected,
2568                    "actual": a.actual,
2569                })
2570            })
2571            .collect();
2572
2573        JsonRpcResponse::success(
2574            id,
2575            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "anomalies": results, "count": results.len() })).unwrap_or_default() }] }),
2576        )
2577    }
2578
2579    // ── 9. Semantic Search ──────────────────────────────────────────────
2580
2581    fn tool_search_semantic(&self, id: Value, args: &Value) -> JsonRpcResponse {
2582        let (_, graph) = match self.resolve_graph(args) {
2583            Ok(g) => g,
2584            Err(e) => return JsonRpcResponse::error(id, e),
2585        };
2586        let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2587        let top_k = args.get("top_k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2588
2589        let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2590        let result = engine.search(query, top_k);
2591
2592        JsonRpcResponse::success(
2593            id,
2594            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2595        )
2596    }
2597
2598    fn tool_search_similar(&self, id: Value, args: &Value) -> JsonRpcResponse {
2599        let (_, graph) = match self.resolve_graph(args) {
2600            Ok(g) => g,
2601            Err(e) => return JsonRpcResponse::error(id, e),
2602        };
2603        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2604        let top_k = args.get("top_k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2605
2606        let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2607        let results = engine.find_similar(unit_id, top_k);
2608
2609        JsonRpcResponse::success(
2610            id,
2611            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "similar": results })).unwrap_or_default() }] }),
2612        )
2613    }
2614
2615    fn tool_search_explain(&self, id: Value, args: &Value) -> JsonRpcResponse {
2616        let (_, graph) = match self.resolve_graph(args) {
2617            Ok(g) => g,
2618            Err(e) => return JsonRpcResponse::error(id, e),
2619        };
2620        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2621        let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2622
2623        let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2624        let explanation = engine.explain_match(unit_id, query);
2625
2626        JsonRpcResponse::success(
2627            id,
2628            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "query": query, "explanation": explanation })).unwrap_or_default() }] }),
2629        )
2630    }
2631
2632    // ── 10. Multi-Codebase Compare ──────────────────────────────────────
2633
2634    fn tool_compare_codebases(&self, id: Value, args: &Value) -> JsonRpcResponse {
2635        let ws_id = match self.resolve_workspace_id(args) {
2636            Ok(id) => id,
2637            Err(e) => return JsonRpcResponse::error(id, e),
2638        };
2639        let workspace = match self.workspace_manager.list(&ws_id) {
2640            Ok(ws) => ws,
2641            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2642        };
2643        if workspace.contexts.len() < 2 {
2644            return JsonRpcResponse::error(
2645                id,
2646                JsonRpcError::invalid_params("Need at least 2 contexts in workspace to compare"),
2647            );
2648        }
2649
2650        let comparer = crate::workspace::compare::CodebaseComparer::new(
2651            &workspace.contexts[0].graph,
2652            &workspace.contexts[0].id,
2653            &workspace.contexts[1].graph,
2654            &workspace.contexts[1].id,
2655        );
2656        let result = comparer.compare();
2657
2658        JsonRpcResponse::success(
2659            id,
2660            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2661        )
2662    }
2663
2664    fn tool_compare_concept(&self, id: Value, args: &Value) -> JsonRpcResponse {
2665        let ws_id = match self.resolve_workspace_id(args) {
2666            Ok(id) => id,
2667            Err(e) => return JsonRpcResponse::error(id, e),
2668        };
2669        let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2670        let workspace = match self.workspace_manager.list(&ws_id) {
2671            Ok(ws) => ws,
2672            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2673        };
2674        if workspace.contexts.len() < 2 {
2675            return JsonRpcResponse::error(
2676                id,
2677                JsonRpcError::invalid_params("Need at least 2 contexts"),
2678            );
2679        }
2680
2681        let comparer = crate::workspace::compare::CodebaseComparer::new(
2682            &workspace.contexts[0].graph,
2683            &workspace.contexts[0].id,
2684            &workspace.contexts[1].graph,
2685            &workspace.contexts[1].id,
2686        );
2687        let result = comparer.compare_concept(concept);
2688
2689        JsonRpcResponse::success(
2690            id,
2691            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2692        )
2693    }
2694
2695    fn tool_compare_migrate(&self, id: Value, args: &Value) -> JsonRpcResponse {
2696        let ws_id = match self.resolve_workspace_id(args) {
2697            Ok(id) => id,
2698            Err(e) => return JsonRpcResponse::error(id, e),
2699        };
2700        let workspace = match self.workspace_manager.list(&ws_id) {
2701            Ok(ws) => ws,
2702            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2703        };
2704        if workspace.contexts.len() < 2 {
2705            return JsonRpcResponse::error(
2706                id,
2707                JsonRpcError::invalid_params("Need at least 2 contexts"),
2708            );
2709        }
2710
2711        let comparer = crate::workspace::compare::CodebaseComparer::new(
2712            &workspace.contexts[0].graph,
2713            &workspace.contexts[0].id,
2714            &workspace.contexts[1].graph,
2715            &workspace.contexts[1].id,
2716        );
2717        let plan = comparer.migration_plan();
2718
2719        JsonRpcResponse::success(
2720            id,
2721            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&plan).unwrap_or_default() }] }),
2722        )
2723    }
2724
2725    // ── 11. Version Archaeology ─────────────────────────────────────────
2726
2727    fn tool_archaeology_node(&self, id: Value, args: &Value) -> JsonRpcResponse {
2728        let (_, graph) = match self.resolve_graph(args) {
2729            Ok(g) => g,
2730            Err(e) => return JsonRpcResponse::error(id, e),
2731        };
2732        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2733
2734        let history = crate::temporal::history::ChangeHistory::new();
2735        let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2736        let result = archaeologist.investigate(unit_id);
2737
2738        JsonRpcResponse::success(
2739            id,
2740            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "result": result })).unwrap_or_default() }] }),
2741        )
2742    }
2743
2744    fn tool_archaeology_why(&self, id: Value, args: &Value) -> JsonRpcResponse {
2745        let (_, graph) = match self.resolve_graph(args) {
2746            Ok(g) => g,
2747            Err(e) => return JsonRpcResponse::error(id, e),
2748        };
2749        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2750
2751        let history = crate::temporal::history::ChangeHistory::new();
2752        let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2753        let result = archaeologist.investigate(unit_id);
2754        let explanation = result
2755            .map(|r| r.why_explanation)
2756            .unwrap_or_else(|| "Unit not found".to_string());
2757
2758        JsonRpcResponse::success(
2759            id,
2760            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "explanation": explanation })).unwrap_or_default() }] }),
2761        )
2762    }
2763
2764    fn tool_archaeology_when(&self, id: Value, args: &Value) -> JsonRpcResponse {
2765        let (_, graph) = match self.resolve_graph(args) {
2766            Ok(g) => g,
2767            Err(e) => return JsonRpcResponse::error(id, e),
2768        };
2769        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2770
2771        let history = crate::temporal::history::ChangeHistory::new();
2772        let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2773        let timeline = archaeologist.when_changed(unit_id);
2774
2775        JsonRpcResponse::success(
2776            id,
2777            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "timeline": timeline })).unwrap_or_default() }] }),
2778        )
2779    }
2780
2781    // ── 12. Pattern Extraction ──────────────────────────────────────────
2782
2783    fn tool_pattern_extract(&self, id: Value, args: &Value) -> JsonRpcResponse {
2784        let (_, graph) = match self.resolve_graph(args) {
2785            Ok(g) => g,
2786            Err(e) => return JsonRpcResponse::error(id, e),
2787        };
2788
2789        let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2790        let patterns = extractor.extract_patterns();
2791
2792        JsonRpcResponse::success(
2793            id,
2794            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&patterns).unwrap_or_default() }] }),
2795        )
2796    }
2797
2798    fn tool_pattern_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2799        let (_, graph) = match self.resolve_graph(args) {
2800            Ok(g) => g,
2801            Err(e) => return JsonRpcResponse::error(id, e),
2802        };
2803        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2804
2805        let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2806        let violations = extractor.check_patterns(unit_id);
2807
2808        JsonRpcResponse::success(
2809            id,
2810            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "violations": violations })).unwrap_or_default() }] }),
2811        )
2812    }
2813
2814    fn tool_pattern_suggest(&self, id: Value, args: &Value) -> JsonRpcResponse {
2815        let (_, graph) = match self.resolve_graph(args) {
2816            Ok(g) => g,
2817            Err(e) => return JsonRpcResponse::error(id, e),
2818        };
2819        let file_path = args.get("file_path").and_then(|v| v.as_str()).unwrap_or("");
2820
2821        let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2822        let suggestions = extractor.suggest_patterns(file_path);
2823
2824        JsonRpcResponse::success(
2825            id,
2826            json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "file_path": file_path, "suggestions": suggestions })).unwrap_or_default() }] }),
2827        )
2828    }
2829
2830    // -- 13. Code Resurrection --------------------------------------------------
2831
2832    fn tool_resurrect_search(&self, id: Value, args: &Value) -> JsonRpcResponse {
2833        let (_, graph) = match self.resolve_graph(args) {
2834            Ok(g) => g,
2835            Err(e) => return JsonRpcResponse::error(id, e),
2836        };
2837        let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2838        let max_results = args
2839            .get("max_results")
2840            .and_then(|v| v.as_u64())
2841            .unwrap_or(10) as usize;
2842        let query_lower = query.to_lowercase();
2843        let mut traces: Vec<Value> = Vec::new();
2844        for unit in graph.units() {
2845            let name_lower = unit.name.to_lowercase();
2846            let doc_lower = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2847            if name_lower.contains(&query_lower) || doc_lower.contains(&query_lower) {
2848                let is_deprecated = doc_lower.contains("deprecated")
2849                    || doc_lower.contains("removed")
2850                    || name_lower.contains("deprecated")
2851                    || name_lower.starts_with("old_");
2852                traces.push(json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "is_deprecated": is_deprecated, "doc": unit.doc_summary, "trace_type": if is_deprecated { "deprecated" } else { "reference" }}));
2853                if traces.len() >= max_results {
2854                    break;
2855                }
2856            }
2857        }
2858        JsonRpcResponse::success(
2859            id,
2860            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "traces_found": traces.len(), "traces": traces})).unwrap_or_default()}]}),
2861        )
2862    }
2863
2864    fn tool_resurrect_attempt(&self, id: Value, args: &Value) -> JsonRpcResponse {
2865        let (_, graph) = match self.resolve_graph(args) {
2866            Ok(g) => g,
2867            Err(e) => return JsonRpcResponse::error(id, e),
2868        };
2869        let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2870        let query_lower = query.to_lowercase();
2871        let mut evidence: Vec<Value> = Vec::new();
2872        for unit in graph.units() {
2873            let name_lower = unit.name.to_lowercase();
2874            let doc_lower = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2875            let sig_lower = unit.signature.as_deref().unwrap_or("").to_lowercase();
2876            if (unit.unit_type == CodeUnitType::Test
2877                && (name_lower.contains(&query_lower) || doc_lower.contains(&query_lower)))
2878                || (unit.unit_type == CodeUnitType::Doc && doc_lower.contains(&query_lower))
2879                || sig_lower.contains(&query_lower)
2880            {
2881                evidence.push(json!({"source": unit.unit_type.label(), "unit_id": unit.id, "name": unit.name, "signature": unit.signature, "doc": unit.doc_summary, "file": unit.file_path.display().to_string()}));
2882            }
2883        }
2884        let status = if evidence.is_empty() {
2885            "insufficient_evidence"
2886        } else {
2887            "partial_reconstruction"
2888        };
2889        let confidence = if evidence.len() > 5 {
2890            "high"
2891        } else if evidence.len() > 2 {
2892            "medium"
2893        } else {
2894            "low"
2895        };
2896        JsonRpcResponse::success(
2897            id,
2898            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "status": status, "confidence": confidence, "evidence_count": evidence.len(), "evidence": evidence})).unwrap_or_default()}]}),
2899        )
2900    }
2901
2902    fn tool_resurrect_verify(&self, id: Value, args: &Value) -> JsonRpcResponse {
2903        let (_, graph) = match self.resolve_graph(args) {
2904            Ok(g) => g,
2905            Err(e) => return JsonRpcResponse::error(id, e),
2906        };
2907        let original_name = args
2908            .get("original_name")
2909            .and_then(|v| v.as_str())
2910            .unwrap_or("");
2911        let reconstructed = args
2912            .get("reconstructed")
2913            .and_then(|v| v.as_str())
2914            .unwrap_or("");
2915        let name_lower = original_name.to_lowercase();
2916        let mut refs = 0u64;
2917        let mut has_tests = false;
2918        for unit in graph.units() {
2919            if unit.name.to_lowercase().contains(&name_lower) {
2920                refs += 1;
2921                if unit.unit_type == CodeUnitType::Test {
2922                    has_tests = true;
2923                }
2924            }
2925        }
2926        let status = if refs > 0 {
2927            "plausible"
2928        } else {
2929            "unverifiable"
2930        };
2931        let confidence = if has_tests && refs > 2 {
2932            "high"
2933        } else if refs > 0 {
2934            "medium"
2935        } else {
2936            "low"
2937        };
2938        JsonRpcResponse::success(
2939            id,
2940            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"original_name": original_name, "reconstructed_length": reconstructed.len(), "references": refs, "has_tests": has_tests, "status": status, "confidence": confidence})).unwrap_or_default()}]}),
2941        )
2942    }
2943
2944    fn tool_resurrect_history(&self, id: Value, args: &Value) -> JsonRpcResponse {
2945        let (_, graph) = match self.resolve_graph(args) {
2946            Ok(g) => g,
2947            Err(e) => return JsonRpcResponse::error(id, e),
2948        };
2949        let mut versions: Vec<Value> = Vec::new();
2950        for edge in graph.edges() {
2951            if edge.edge_type == EdgeType::VersionOf {
2952                let src = graph
2953                    .get_unit(edge.source_id)
2954                    .map(|u| u.name.as_str())
2955                    .unwrap_or("?");
2956                let tgt = graph
2957                    .get_unit(edge.target_id)
2958                    .map(|u| u.name.as_str())
2959                    .unwrap_or("?");
2960                versions.push(json!({"newer_id": edge.source_id, "newer": src, "older_id": edge.target_id, "older": tgt}));
2961            }
2962        }
2963        let mut deprecated: Vec<Value> = Vec::new();
2964        for unit in graph.units() {
2965            let doc = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2966            if doc.contains("deprecated") || unit.name.to_lowercase().contains("deprecated") {
2967                deprecated.push(
2968                    json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label()}),
2969                );
2970            }
2971        }
2972        JsonRpcResponse::success(
2973            id,
2974            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"versions": versions, "deprecated": deprecated})).unwrap_or_default()}]}),
2975        )
2976    }
2977
2978    // -- 14. Code Genetics ------------------------------------------------------
2979
2980    fn tool_genetics_dna(&self, id: Value, args: &Value) -> JsonRpcResponse {
2981        let (_, graph) = match self.resolve_graph(args) {
2982            Ok(g) => g,
2983            Err(e) => return JsonRpcResponse::error(id, e),
2984        };
2985        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2986        let unit = match graph.get_unit(unit_id) {
2987            Some(u) => u,
2988            None => {
2989                return JsonRpcResponse::error(
2990                    id,
2991                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
2992                )
2993            }
2994        };
2995        let outgoing = graph.edges_from(unit_id);
2996        let incoming = graph.edges_to(unit_id);
2997        let mut out_types: Vec<&str> = outgoing.iter().map(|e| e.edge_type.label()).collect();
2998        out_types.sort();
2999        out_types.dedup();
3000        let mut in_types: Vec<&str> = incoming.iter().map(|e| e.edge_type.label()).collect();
3001        in_types.sort();
3002        in_types.dedup();
3003        let naming = if unit.name.contains('_') {
3004            "snake_case"
3005        } else if unit
3006            .name
3007            .chars()
3008            .next()
3009            .map(|c| c.is_uppercase())
3010            .unwrap_or(false)
3011        {
3012            "PascalCase"
3013        } else {
3014            "camelCase"
3015        };
3016        JsonRpcResponse::success(
3017            id,
3018            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "type": unit.unit_type.label(), "naming": naming, "complexity": unit.complexity, "is_async": unit.is_async, "visibility": format!("{:?}", unit.visibility), "out_edge_types": out_types, "in_edge_types": in_types, "out_count": outgoing.len(), "in_count": incoming.len(), "has_tests": incoming.iter().any(|e| e.edge_type == EdgeType::Tests), "has_docs": incoming.iter().any(|e| e.edge_type == EdgeType::Documents), "stability": unit.stability_score, "signature": unit.signature})).unwrap_or_default()}]}),
3019        )
3020    }
3021
3022    fn tool_genetics_lineage(&self, id: Value, args: &Value) -> JsonRpcResponse {
3023        let (_, graph) = match self.resolve_graph(args) {
3024            Ok(g) => g,
3025            Err(e) => return JsonRpcResponse::error(id, e),
3026        };
3027        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3028        let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
3029        let mut lineage: Vec<Value> = Vec::new();
3030        let mut visited = std::collections::HashSet::new();
3031        let mut frontier = vec![(unit_id, 0usize)];
3032        while let Some((current, depth)) = frontier.pop() {
3033            if depth > max_depth || !visited.insert(current) {
3034                continue;
3035            }
3036            if let Some(unit) = graph.get_unit(current) {
3037                lineage.push(json!({"unit_id": current, "name": unit.name, "type": unit.unit_type.label(), "depth": depth, "file": unit.file_path.display().to_string()}));
3038                for edge in graph.edges_to(current) {
3039                    if matches!(
3040                        edge.edge_type,
3041                        EdgeType::Contains
3042                            | EdgeType::Inherits
3043                            | EdgeType::VersionOf
3044                            | EdgeType::Implements
3045                    ) {
3046                        frontier.push((edge.source_id, depth + 1));
3047                    }
3048                }
3049            }
3050        }
3051        JsonRpcResponse::success(
3052            id,
3053            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "lineage_depth": lineage.len(), "lineage": lineage})).unwrap_or_default()}]}),
3054        )
3055    }
3056
3057    fn tool_genetics_mutations(&self, id: Value, args: &Value) -> JsonRpcResponse {
3058        let (_, graph) = match self.resolve_graph(args) {
3059            Ok(g) => g,
3060            Err(e) => return JsonRpcResponse::error(id, e),
3061        };
3062        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3063        let unit = match graph.get_unit(unit_id) {
3064            Some(u) => u,
3065            None => {
3066                return JsonRpcResponse::error(
3067                    id,
3068                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3069                )
3070            }
3071        };
3072        let mut mutations: Vec<Value> = Vec::new();
3073        if unit.complexity > 20 {
3074            mutations.push(json!({"type": "complexity_mutation", "description": format!("Complexity {} is unusually high", unit.complexity), "severity": "medium"}));
3075        }
3076        if unit.stability_score < 0.3 && unit.change_count > 5 {
3077            mutations.push(json!({"type": "stability_mutation", "description": format!("Low stability ({:.2}) with {} changes", unit.stability_score, unit.change_count), "severity": "high"}));
3078        }
3079        let breaks = graph
3080            .edges_from(unit_id)
3081            .into_iter()
3082            .filter(|e| e.edge_type == EdgeType::BreaksWith)
3083            .count();
3084        if breaks > 0 {
3085            mutations.push(json!({"type": "breaking_mutation", "description": format!("{} breaking relationships", breaks), "severity": "high"}));
3086        }
3087        // Check naming mutation vs siblings
3088        let parents: Vec<_> = graph
3089            .edges_to(unit_id)
3090            .into_iter()
3091            .filter(|e| e.edge_type == EdgeType::Contains)
3092            .collect();
3093        for pe in &parents {
3094            let sibs: Vec<_> = graph
3095                .edges_from(pe.source_id)
3096                .into_iter()
3097                .filter(|e| e.edge_type == EdgeType::Contains && e.target_id != unit_id)
3098                .collect();
3099            let unit_snake = unit.name.contains('_');
3100            for se in &sibs {
3101                if let Some(sib) = graph.get_unit(se.target_id) {
3102                    if sib.unit_type == unit.unit_type && sib.name.contains('_') != unit_snake {
3103                        mutations.push(json!({"type": "naming_mutation", "description": format!("'{}' differs from sibling '{}'", unit.name, sib.name), "severity": "low"}));
3104                        break;
3105                    }
3106                }
3107            }
3108        }
3109        JsonRpcResponse::success(
3110            id,
3111            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "mutations": mutations})).unwrap_or_default()}]}),
3112        )
3113    }
3114
3115    fn tool_genetics_diseases(&self, id: Value, args: &Value) -> JsonRpcResponse {
3116        let (_, graph) = match self.resolve_graph(args) {
3117            Ok(g) => g,
3118            Err(e) => return JsonRpcResponse::error(id, e),
3119        };
3120        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3121        let unit = match graph.get_unit(unit_id) {
3122            Some(u) => u,
3123            None => {
3124                return JsonRpcResponse::error(
3125                    id,
3126                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3127                )
3128            }
3129        };
3130        let mut diseases: Vec<Value> = Vec::new();
3131        let out = graph.edges_from(unit_id);
3132        let inc = graph.edges_to(unit_id);
3133        if out.len() > 20 {
3134            diseases.push(json!({"disease": "god_object", "severity": "high", "detail": format!("{} outgoing edges", out.len())}));
3135        }
3136        let targets: std::collections::HashSet<u64> = out.iter().map(|e| e.target_id).collect();
3137        let sources: std::collections::HashSet<u64> = inc.iter().map(|e| e.source_id).collect();
3138        let circular: Vec<u64> = targets.intersection(&sources).copied().collect();
3139        if !circular.is_empty() {
3140            diseases.push(
3141                json!({"disease": "circular_dependency", "severity": "high", "with": circular}),
3142            );
3143        }
3144        let calls_out = out
3145            .iter()
3146            .filter(|e| e.edge_type == EdgeType::Calls)
3147            .count();
3148        let calls_in = inc
3149            .iter()
3150            .filter(|e| e.edge_type == EdgeType::Calls)
3151            .count();
3152        if calls_out > 10 && calls_out > calls_in * 3 {
3153            diseases.push(json!({"disease": "feature_envy", "severity": "medium", "calls_out": calls_out, "calls_in": calls_in}));
3154        }
3155        if !inc.iter().any(|e| e.edge_type == EdgeType::Tests)
3156            && unit.unit_type == CodeUnitType::Function
3157        {
3158            diseases.push(json!({"disease": "untested", "severity": "medium"}));
3159        }
3160        let non_contains = inc
3161            .iter()
3162            .filter(|e| e.edge_type != EdgeType::Contains)
3163            .count();
3164        if non_contains == 0 && !inc.is_empty() {
3165            diseases.push(json!({"disease": "orphan_code", "severity": "medium"}));
3166        }
3167        JsonRpcResponse::success(
3168            id,
3169            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "diseases": diseases})).unwrap_or_default()}]}),
3170        )
3171    }
3172
3173    // -- 15. Code Telepathy -----------------------------------------------------
3174
3175    fn tool_telepathy_connect(&self, id: Value, args: &Value) -> JsonRpcResponse {
3176        let ws_id = match self.resolve_workspace_id(args) {
3177            Ok(ws) => ws,
3178            Err(e) => return JsonRpcResponse::error(id, e),
3179        };
3180        let workspace = match self.workspace_manager.list(&ws_id) {
3181            Ok(ws) => ws,
3182            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3183        };
3184        let connections: Vec<Value> = workspace.contexts.iter().map(|c| json!({"context_id": c.id, "role": c.role.label(), "path": c.path, "units": c.graph.units().len()})).collect();
3185        JsonRpcResponse::success(
3186            id,
3187            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "status": "connected", "connections": connections})).unwrap_or_default()}]}),
3188        )
3189    }
3190
3191    fn tool_telepathy_broadcast(&self, id: Value, args: &Value) -> JsonRpcResponse {
3192        let ws_id = match self.resolve_workspace_id(args) {
3193            Ok(ws) => ws,
3194            Err(e) => return JsonRpcResponse::error(id, e),
3195        };
3196        let insight = args.get("insight").and_then(|v| v.as_str()).unwrap_or("");
3197        let source_graph = args
3198            .get("source_graph")
3199            .and_then(|v| v.as_str())
3200            .unwrap_or("");
3201        let results = match self.workspace_manager.query_all(&ws_id, insight) {
3202            Ok(r) => r,
3203            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3204        };
3205        let receivers: Vec<Value> = results.iter().map(|r| json!({"context_id": r.context_id, "role": r.context_role.label(), "matches": r.matches.len()})).collect();
3206        JsonRpcResponse::success(
3207            id,
3208            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "insight": insight, "source": source_graph, "receivers": receivers})).unwrap_or_default()}]}),
3209        )
3210    }
3211
3212    fn tool_telepathy_listen(&self, id: Value, args: &Value) -> JsonRpcResponse {
3213        let ws_id = match self.resolve_workspace_id(args) {
3214            Ok(ws) => ws,
3215            Err(e) => return JsonRpcResponse::error(id, e),
3216        };
3217        let workspace = match self.workspace_manager.list(&ws_id) {
3218            Ok(ws) => ws,
3219            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3220        };
3221        let mut insights: Vec<Value> = Vec::new();
3222        for ctx in &workspace.contexts {
3223            let high_cx: Vec<&str> = ctx
3224                .graph
3225                .units()
3226                .iter()
3227                .filter(|u| u.complexity > 15)
3228                .take(5)
3229                .map(|u| u.name.as_str())
3230                .collect();
3231            if !high_cx.is_empty() {
3232                insights.push(json!({"context": ctx.id, "role": ctx.role.label(), "type": "high_complexity", "units": high_cx}));
3233            }
3234            let tests = ctx
3235                .graph
3236                .units()
3237                .iter()
3238                .filter(|u| u.unit_type == CodeUnitType::Test)
3239                .count();
3240            let funcs = ctx
3241                .graph
3242                .units()
3243                .iter()
3244                .filter(|u| u.unit_type == CodeUnitType::Function)
3245                .count();
3246            if funcs > 0 {
3247                insights.push(json!({"context": ctx.id, "role": ctx.role.label(), "type": "test_ratio", "tests": tests, "functions": funcs, "ratio": tests as f64 / funcs as f64}));
3248            }
3249        }
3250        JsonRpcResponse::success(
3251            id,
3252            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "insights": insights})).unwrap_or_default()}]}),
3253        )
3254    }
3255
3256    fn tool_telepathy_consensus(&self, id: Value, args: &Value) -> JsonRpcResponse {
3257        let ws_id = match self.resolve_workspace_id(args) {
3258            Ok(ws) => ws,
3259            Err(e) => return JsonRpcResponse::error(id, e),
3260        };
3261        let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
3262        let results = match self.workspace_manager.query_all(&ws_id, concept) {
3263            Ok(r) => r,
3264            Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3265        };
3266        let total = results.len();
3267        let with_matches = results.iter().filter(|r| !r.matches.is_empty()).count();
3268        let level = if with_matches == total && total > 0 {
3269            "universal"
3270        } else if with_matches as f64 / total.max(1) as f64 > 0.5 {
3271            "majority"
3272        } else if with_matches > 0 {
3273            "minority"
3274        } else {
3275            "none"
3276        };
3277        let details: Vec<Value> = results.iter().map(|r| json!({"context_id": r.context_id, "role": r.context_role.label(), "has_concept": !r.matches.is_empty(), "count": r.matches.len()})).collect();
3278        JsonRpcResponse::success(
3279            id,
3280            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"concept": concept, "consensus": level, "total": total, "with_concept": with_matches, "details": details})).unwrap_or_default()}]}),
3281        )
3282    }
3283
3284    // -- 16. Code Soul ----------------------------------------------------------
3285
3286    fn tool_soul_extract(&self, id: Value, args: &Value) -> JsonRpcResponse {
3287        let (_, graph) = match self.resolve_graph(args) {
3288            Ok(g) => g,
3289            Err(e) => return JsonRpcResponse::error(id, e),
3290        };
3291        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3292        let unit = match graph.get_unit(unit_id) {
3293            Some(u) => u,
3294            None => {
3295                return JsonRpcResponse::error(
3296                    id,
3297                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3298                )
3299            }
3300        };
3301        let out = graph.edges_from(unit_id);
3302        let inc = graph.edges_to(unit_id);
3303        let purpose = format!(
3304            "{} {} that {}",
3305            format!("{:?}", unit.visibility).to_lowercase(),
3306            unit.unit_type.label(),
3307            if out.iter().any(|e| e.edge_type == EdgeType::Calls) {
3308                "orchestrates calls"
3309            } else if unit.unit_type == CodeUnitType::Test {
3310                "verifies behavior"
3311            } else if unit.unit_type == CodeUnitType::Doc {
3312                "documents knowledge"
3313            } else {
3314                "provides functionality"
3315            }
3316        );
3317        let mut values: Vec<&str> = Vec::new();
3318        if inc.iter().any(|e| e.edge_type == EdgeType::Tests) {
3319            values.push("correctness");
3320        }
3321        if inc.iter().any(|e| e.edge_type == EdgeType::Documents) {
3322            values.push("documentation");
3323        }
3324        if unit.stability_score > 0.8 {
3325            values.push("stability");
3326        }
3327        if unit.complexity < 5 {
3328            values.push("simplicity");
3329        }
3330        if unit.is_async {
3331            values.push("concurrency");
3332        }
3333        let deps: Vec<String> = out
3334            .iter()
3335            .filter_map(|e| {
3336                graph
3337                    .get_unit(e.target_id)
3338                    .map(|u| format!("{}:{}", e.edge_type.label(), u.name.clone()))
3339            })
3340            .take(10)
3341            .collect();
3342        JsonRpcResponse::success(
3343            id,
3344            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "soul_id": format!("soul-{}-{}", unit_id, unit.name), "purpose": purpose, "values": values, "dependencies": deps, "signature": unit.signature, "complexity": unit.complexity, "stability": unit.stability_score, "language": unit.language.name()})).unwrap_or_default()}]}),
3345        )
3346    }
3347
3348    fn tool_soul_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
3349        let (_, graph) = match self.resolve_graph(args) {
3350            Ok(g) => g,
3351            Err(e) => return JsonRpcResponse::error(id, e),
3352        };
3353        let id_a = args.get("unit_id_a").and_then(|v| v.as_u64()).unwrap_or(0);
3354        let id_b = args.get("unit_id_b").and_then(|v| v.as_u64()).unwrap_or(0);
3355        let ua = match graph.get_unit(id_a) {
3356            Some(u) => u,
3357            None => {
3358                return JsonRpcResponse::error(
3359                    id,
3360                    JsonRpcError::invalid_params(format!("Unit {} not found", id_a)),
3361                )
3362            }
3363        };
3364        let ub = match graph.get_unit(id_b) {
3365            Some(u) => u,
3366            None => {
3367                return JsonRpcResponse::error(
3368                    id,
3369                    JsonRpcError::invalid_params(format!("Unit {} not found", id_b)),
3370                )
3371            }
3372        };
3373        let ea: std::collections::HashSet<String> = graph
3374            .edges_from(id_a)
3375            .iter()
3376            .map(|e| e.edge_type.label().to_string())
3377            .collect();
3378        let eb: std::collections::HashSet<String> = graph
3379            .edges_from(id_b)
3380            .iter()
3381            .map(|e| e.edge_type.label().to_string())
3382            .collect();
3383        let shared: Vec<&String> = ea.intersection(&eb).collect();
3384        let type_match = ua.unit_type == ub.unit_type;
3385        let cdiff = (ua.complexity as i64 - ub.complexity as i64).unsigned_abs();
3386        let mut sim = 0.0f64;
3387        if type_match {
3388            sim += 0.3;
3389        }
3390        if ua.is_async == ub.is_async {
3391            sim += 0.1;
3392        }
3393        if cdiff < 5 {
3394            sim += 0.2;
3395        }
3396        sim += 0.4 * (shared.len() as f64 / ea.len().max(eb.len()).max(1) as f64);
3397        let verdict = if sim > 0.8 {
3398            "same_soul"
3399        } else if sim > 0.5 {
3400            "related_souls"
3401        } else {
3402            "different_souls"
3403        };
3404        JsonRpcResponse::success(
3405            id,
3406            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_a": {"id": id_a, "name": ua.name}, "unit_b": {"id": id_b, "name": ub.name}, "similarity": sim, "type_match": type_match, "complexity_diff": cdiff, "shared_edges": shared, "verdict": verdict})).unwrap_or_default()}]}),
3407        )
3408    }
3409
3410    fn tool_soul_preserve(&self, id: Value, args: &Value) -> JsonRpcResponse {
3411        let (_, graph) = match self.resolve_graph(args) {
3412            Ok(g) => g,
3413            Err(e) => return JsonRpcResponse::error(id, e),
3414        };
3415        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3416        let new_lang = args
3417            .get("new_language")
3418            .and_then(|v| v.as_str())
3419            .unwrap_or("unknown");
3420        let unit = match graph.get_unit(unit_id) {
3421            Some(u) => u,
3422            None => {
3423                return JsonRpcResponse::error(
3424                    id,
3425                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3426                )
3427            }
3428        };
3429        let out = graph.edges_from(unit_id);
3430        let inc = graph.edges_to(unit_id);
3431        let risk = if out.len() > 10 {
3432            "high"
3433        } else if out.len() > 5 {
3434            "medium"
3435        } else {
3436            "low"
3437        };
3438        JsonRpcResponse::success(
3439            id,
3440            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "original_language": unit.language.name(), "target_language": new_lang, "soul_id": format!("soul-{}-{}", unit_id, unit.name), "purpose": unit.doc_summary, "signature": unit.signature, "deps": out.len(), "dependents": inc.len(), "is_async": unit.is_async, "tests": inc.iter().filter(|e| e.edge_type == EdgeType::Tests).count(), "risk": risk})).unwrap_or_default()}]}),
3441        )
3442    }
3443
3444    fn tool_soul_reincarnate(&self, id: Value, args: &Value) -> JsonRpcResponse {
3445        let (_gn, graph) = match self.resolve_graph(args) {
3446            Ok(g) => g,
3447            Err(e) => return JsonRpcResponse::error(id, e),
3448        };
3449        let soul_id = args.get("soul_id").and_then(|v| v.as_str()).unwrap_or("");
3450        let target_ctx = args
3451            .get("target_context")
3452            .and_then(|v| v.as_str())
3453            .unwrap_or("");
3454        let uid: u64 = soul_id
3455            .strip_prefix("soul-")
3456            .and_then(|s| s.split('-').next())
3457            .and_then(|s| s.parse().ok())
3458            .unwrap_or(0);
3459        let guide = if let Some(unit) = graph.get_unit(uid) {
3460            let deps: Vec<String> = graph
3461                .edges_from(uid)
3462                .iter()
3463                .filter_map(|e| graph.get_unit(e.target_id).map(|u| u.name.clone()))
3464                .take(10)
3465                .collect();
3466            json!({"soul_id": soul_id, "target": target_ctx, "status": "guidance_ready", "name": unit.name, "type": unit.unit_type.label(), "signature": unit.signature, "purpose": unit.doc_summary, "deps": deps})
3467        } else {
3468            json!({"soul_id": soul_id, "target": target_ctx, "status": "soul_not_found"})
3469        };
3470        JsonRpcResponse::success(
3471            id,
3472            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&guide).unwrap_or_default()}]}),
3473        )
3474    }
3475
3476    fn tool_soul_karma(&self, id: Value, args: &Value) -> JsonRpcResponse {
3477        let (_, graph) = match self.resolve_graph(args) {
3478            Ok(g) => g,
3479            Err(e) => return JsonRpcResponse::error(id, e),
3480        };
3481        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3482        let unit = match graph.get_unit(unit_id) {
3483            Some(u) => u,
3484            None => {
3485                return JsonRpcResponse::error(
3486                    id,
3487                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3488                )
3489            }
3490        };
3491        let inc = graph.edges_to(unit_id);
3492        let out = graph.edges_from(unit_id);
3493        let mut pos = 0i64;
3494        let mut neg = 0i64;
3495        let mut details: Vec<Value> = Vec::new();
3496        let tc = inc
3497            .iter()
3498            .filter(|e| e.edge_type == EdgeType::Tests)
3499            .count();
3500        if tc > 0 {
3501            pos += tc as i64 * 10;
3502            details
3503                .push(json!({"k": "positive", "r": format!("{} tests", tc), "p": tc as i64 * 10}));
3504        }
3505        let dc = inc
3506            .iter()
3507            .filter(|e| e.edge_type == EdgeType::Documents)
3508            .count();
3509        if dc > 0 {
3510            pos += dc as i64 * 5;
3511            details.push(json!({"k": "positive", "r": format!("{} docs", dc), "p": dc as i64 * 5}));
3512        }
3513        if unit.stability_score > 0.8 {
3514            pos += 15;
3515            details.push(json!({"k": "positive", "r": "high stability", "p": 15}));
3516        }
3517        if unit.complexity < 10 {
3518            pos += 10;
3519            details.push(json!({"k": "positive", "r": "low complexity", "p": 10}));
3520        }
3521        let br = out
3522            .iter()
3523            .filter(|e| e.edge_type == EdgeType::BreaksWith)
3524            .count();
3525        if br > 0 {
3526            neg += br as i64 * 20;
3527            details.push(
3528                json!({"k": "negative", "r": format!("{} breaks", br), "p": -(br as i64 * 20)}),
3529            );
3530        }
3531        if unit.complexity > 20 {
3532            neg += 15;
3533            details.push(json!({"k": "negative", "r": "very high complexity", "p": -15}));
3534        }
3535        if tc == 0 && unit.unit_type == CodeUnitType::Function {
3536            neg += 10;
3537            details.push(json!({"k": "negative", "r": "no tests", "p": -10}));
3538        }
3539        if unit.stability_score < 0.3 {
3540            neg += 10;
3541            details.push(json!({"k": "negative", "r": "low stability", "p": -10}));
3542        }
3543        let total = pos - neg;
3544        let level = if total > 30 {
3545            "enlightened"
3546        } else if total > 10 {
3547            "good"
3548        } else if total > -10 {
3549            "neutral"
3550        } else {
3551            "troubled"
3552        };
3553        JsonRpcResponse::success(
3554            id,
3555            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "karma": total, "positive": pos, "negative": neg, "level": level, "details": details})).unwrap_or_default()}]}),
3556        )
3557    }
3558
3559    // -- 17. Code Omniscience ---------------------------------------------------
3560
3561    fn tool_omniscience_search(&self, id: Value, args: &Value) -> JsonRpcResponse {
3562        let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
3563        let languages: Vec<String> = args
3564            .get("languages")
3565            .and_then(|v| v.as_array())
3566            .map(|a| {
3567                a.iter()
3568                    .filter_map(|v| v.as_str().map(String::from))
3569                    .collect()
3570            })
3571            .unwrap_or_default();
3572        let max_results = args
3573            .get("max_results")
3574            .and_then(|v| v.as_u64())
3575            .unwrap_or(10) as usize;
3576        let ql = query.to_lowercase();
3577        let mut results: Vec<Value> = Vec::new();
3578        for (gn, graph) in &self.graphs {
3579            for unit in graph.units() {
3580                if results.len() >= max_results {
3581                    break;
3582                }
3583                if !languages.is_empty()
3584                    && !languages
3585                        .iter()
3586                        .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3587                {
3588                    continue;
3589                }
3590                if unit.name.to_lowercase().contains(&ql)
3591                    || unit.qualified_name.to_lowercase().contains(&ql)
3592                {
3593                    results.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "qualified_name": unit.qualified_name, "type": unit.unit_type.label(), "language": unit.language.name(), "file": unit.file_path.display().to_string()}));
3594                }
3595            }
3596        }
3597        JsonRpcResponse::success(
3598            id,
3599            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "count": results.len(), "results": results})).unwrap_or_default()}]}),
3600        )
3601    }
3602
3603    fn tool_omniscience_best(&self, id: Value, args: &Value) -> JsonRpcResponse {
3604        let cap = args
3605            .get("capability")
3606            .and_then(|v| v.as_str())
3607            .unwrap_or("");
3608        let criteria: Vec<String> = args
3609            .get("criteria")
3610            .and_then(|v| v.as_array())
3611            .map(|a| {
3612                a.iter()
3613                    .filter_map(|v| v.as_str().map(String::from))
3614                    .collect()
3615            })
3616            .unwrap_or_default();
3617        let cl = cap.to_lowercase();
3618        let mut cands: Vec<Value> = Vec::new();
3619        for (gn, graph) in &self.graphs {
3620            for unit in graph.units() {
3621                if unit.name.to_lowercase().contains(&cl)
3622                    || unit.qualified_name.to_lowercase().contains(&cl)
3623                {
3624                    let inc = graph.edges_to(unit.id);
3625                    let has_t = inc.iter().any(|e| e.edge_type == EdgeType::Tests);
3626                    let has_d = inc.iter().any(|e| e.edge_type == EdgeType::Documents);
3627                    let mut s = 0.15f64;
3628                    if has_t {
3629                        s += 0.3;
3630                    }
3631                    if has_d {
3632                        s += 0.2;
3633                    }
3634                    if unit.stability_score > 0.7 {
3635                        s += 0.2;
3636                    }
3637                    if unit.complexity < 15 {
3638                        s += 0.15;
3639                    }
3640                    cands.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "score": s, "has_tests": has_t, "has_docs": has_d, "stability": unit.stability_score, "complexity": unit.complexity}));
3641                }
3642            }
3643        }
3644        cands.sort_by(|a, b| {
3645            b["score"]
3646                .as_f64()
3647                .unwrap_or(0.0)
3648                .partial_cmp(&a["score"].as_f64().unwrap_or(0.0))
3649                .unwrap_or(std::cmp::Ordering::Equal)
3650        });
3651        cands.truncate(5);
3652        JsonRpcResponse::success(
3653            id,
3654            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"capability": cap, "criteria": criteria, "best": cands})).unwrap_or_default()}]}),
3655        )
3656    }
3657
3658    fn tool_omniscience_census(&self, id: Value, args: &Value) -> JsonRpcResponse {
3659        let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
3660        let languages: Vec<String> = args
3661            .get("languages")
3662            .and_then(|v| v.as_array())
3663            .map(|a| {
3664                a.iter()
3665                    .filter_map(|v| v.as_str().map(String::from))
3666                    .collect()
3667            })
3668            .unwrap_or_default();
3669        let cl = concept.to_lowercase();
3670        let mut by_lang: HashMap<String, usize> = HashMap::new();
3671        let mut by_type: HashMap<String, usize> = HashMap::new();
3672        let mut total = 0usize;
3673        for graph in self.graphs.values() {
3674            for unit in graph.units() {
3675                if !languages.is_empty()
3676                    && !languages
3677                        .iter()
3678                        .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3679                {
3680                    continue;
3681                }
3682                if unit.name.to_lowercase().contains(&cl)
3683                    || unit.qualified_name.to_lowercase().contains(&cl)
3684                {
3685                    total += 1;
3686                    *by_lang.entry(unit.language.name().to_string()).or_insert(0) += 1;
3687                    *by_type
3688                        .entry(unit.unit_type.label().to_string())
3689                        .or_insert(0) += 1;
3690                }
3691            }
3692        }
3693        JsonRpcResponse::success(
3694            id,
3695            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"concept": concept, "total": total, "by_language": by_lang, "by_type": by_type, "graphs": self.graphs.len()})).unwrap_or_default()}]}),
3696        )
3697    }
3698
3699    fn tool_omniscience_vuln(&self, id: Value, args: &Value) -> JsonRpcResponse {
3700        let (_, graph) = match self.resolve_graph(args) {
3701            Ok(g) => g,
3702            Err(e) => return JsonRpcResponse::error(id, e),
3703        };
3704        let pattern = args.get("pattern").and_then(|v| v.as_str()).unwrap_or("");
3705        let cve = args.get("cve").and_then(|v| v.as_str()).unwrap_or("");
3706        let kws: Vec<&str> = if pattern.is_empty() {
3707            vec![
3708                "unsafe",
3709                "eval",
3710                "exec",
3711                "sql",
3712                "inject",
3713                "deserialize",
3714                "shell",
3715            ]
3716        } else {
3717            vec![pattern]
3718        };
3719        let mut findings: Vec<Value> = Vec::new();
3720        for unit in graph.units() {
3721            let nl = unit.name.to_lowercase();
3722            let sl = unit.signature.as_deref().unwrap_or("").to_lowercase();
3723            for &kw in &kws {
3724                if nl.contains(kw) || sl.contains(kw) {
3725                    let sev = if kw == "unsafe" || kw == "eval" || kw == "exec" {
3726                        "high"
3727                    } else {
3728                        "medium"
3729                    };
3730                    findings.push(json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "pattern": kw, "severity": sev}));
3731                    break;
3732                }
3733            }
3734        }
3735        JsonRpcResponse::success(
3736            id,
3737            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"pattern": pattern, "cve": cve, "count": findings.len(), "findings": findings})).unwrap_or_default()}]}),
3738        )
3739    }
3740
3741    fn tool_omniscience_trend(&self, id: Value, args: &Value) -> JsonRpcResponse {
3742        let domain = args.get("domain").and_then(|v| v.as_str()).unwrap_or("");
3743        let dl = domain.to_lowercase();
3744        let mut trends: Vec<Value> = Vec::new();
3745        for (gn, graph) in &self.graphs {
3746            let m: Vec<_> = graph
3747                .units()
3748                .iter()
3749                .filter(|u| {
3750                    u.name.to_lowercase().contains(&dl)
3751                        || u.qualified_name.to_lowercase().contains(&dl)
3752                })
3753                .collect();
3754            if !m.is_empty() {
3755                let avg_s: f64 =
3756                    m.iter().map(|u| u.stability_score as f64).sum::<f64>() / m.len() as f64;
3757                let avg_c: f64 =
3758                    m.iter().map(|u| u.change_count as f64).sum::<f64>() / m.len() as f64;
3759                let avg_x: f64 =
3760                    m.iter().map(|u| u.complexity as f64).sum::<f64>() / m.len() as f64;
3761                let dir = if avg_c > 5.0 && avg_s < 0.5 {
3762                    "declining"
3763                } else if avg_s > 0.7 && avg_x < 15.0 {
3764                    "stable"
3765                } else {
3766                    "emerging"
3767                };
3768                trends.push(json!({"graph": gn, "count": m.len(), "avg_stability": avg_s, "avg_changes": avg_c, "avg_complexity": avg_x, "trend": dir}));
3769            }
3770        }
3771        JsonRpcResponse::success(
3772            id,
3773            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"domain": domain, "graphs": self.graphs.len(), "trends": trends})).unwrap_or_default()}]}),
3774        )
3775    }
3776
3777    fn tool_omniscience_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
3778        let (_, graph) = match self.resolve_graph(args) {
3779            Ok(g) => g,
3780            Err(e) => return JsonRpcResponse::error(id, e),
3781        };
3782        let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3783        let unit = match graph.get_unit(unit_id) {
3784            Some(u) => u,
3785            None => {
3786                return JsonRpcResponse::error(
3787                    id,
3788                    JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3789                )
3790            }
3791        };
3792        let inc = graph.edges_to(unit_id);
3793        let out = graph.edges_from(unit_id);
3794        let has_t = inc.iter().any(|e| e.edge_type == EdgeType::Tests);
3795        let has_d = inc.iter().any(|e| e.edge_type == EdgeType::Documents);
3796        let mut practices: Vec<Value> = Vec::new();
3797        let mut score = 0u32;
3798        if has_t {
3799            score += 20;
3800            practices.push(json!({"p": "tests", "s": "pass", "pts": 20}));
3801        } else {
3802            practices.push(json!({"p": "tests", "s": "fail", "rec": "Add tests"}));
3803        }
3804        if has_d {
3805            score += 15;
3806            practices.push(json!({"p": "docs", "s": "pass", "pts": 15}));
3807        } else {
3808            practices.push(json!({"p": "docs", "s": "fail", "rec": "Add docs"}));
3809        }
3810        if unit.complexity < 10 {
3811            score += 20;
3812            practices.push(json!({"p": "complexity", "s": "pass", "pts": 20}));
3813        } else if unit.complexity < 20 {
3814            score += 10;
3815            practices.push(json!({"p": "complexity", "s": "warn", "pts": 10}));
3816        } else {
3817            practices.push(json!({"p": "complexity", "s": "fail", "rec": "Refactor"}));
3818        }
3819        if unit.stability_score > 0.7 {
3820            score += 15;
3821            practices.push(json!({"p": "stability", "s": "pass", "pts": 15}));
3822        } else {
3823            practices.push(json!({"p": "stability", "s": "fail", "rec": "Stabilize"}));
3824        }
3825        if out.len() < 10 {
3826            score += 15;
3827            practices.push(json!({"p": "coupling", "s": "pass", "pts": 15}));
3828        } else {
3829            practices.push(json!({"p": "coupling", "s": "fail", "rec": "Reduce deps"}));
3830        }
3831        if unit.doc_summary.is_some() {
3832            score += 15;
3833            practices.push(json!({"p": "doc_summary", "s": "pass", "pts": 15}));
3834        } else {
3835            practices.push(json!({"p": "doc_summary", "s": "fail", "rec": "Add summary"}));
3836        }
3837        let grade = if score >= 80 {
3838            "A"
3839        } else if score >= 60 {
3840            "B"
3841        } else if score >= 40 {
3842            "C"
3843        } else {
3844            "D"
3845        };
3846        JsonRpcResponse::success(
3847            id,
3848            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "score": score, "max": 100, "grade": grade, "practices": practices})).unwrap_or_default()}]}),
3849        )
3850    }
3851
3852    fn tool_omniscience_api_usage(&self, id: Value, args: &Value) -> JsonRpcResponse {
3853        let api = args.get("api").and_then(|v| v.as_str()).unwrap_or("");
3854        let method = args.get("method").and_then(|v| v.as_str()).unwrap_or("");
3855        let al = api.to_lowercase();
3856        let ml = method.to_lowercase();
3857        let mut usages: Vec<Value> = Vec::new();
3858        for (gn, graph) in &self.graphs {
3859            for unit in graph.units() {
3860                let nl = unit.name.to_lowercase();
3861                let ql = unit.qualified_name.to_lowercase();
3862                let sl = unit.signature.as_deref().unwrap_or("").to_lowercase();
3863                let ma = nl.contains(&al) || ql.contains(&al) || sl.contains(&al);
3864                let mm = method.is_empty() || nl.contains(&ml) || sl.contains(&ml);
3865                if ma && mm {
3866                    usages.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "qualified_name": unit.qualified_name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "signature": unit.signature}));
3867                }
3868            }
3869        }
3870        JsonRpcResponse::success(
3871            id,
3872            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"api": api, "method": method, "count": usages.len(), "usages": usages})).unwrap_or_default()}]}),
3873        )
3874    }
3875
3876    fn tool_omniscience_solve(&self, id: Value, args: &Value) -> JsonRpcResponse {
3877        let problem = args.get("problem").and_then(|v| v.as_str()).unwrap_or("");
3878        let languages: Vec<String> = args
3879            .get("languages")
3880            .and_then(|v| v.as_array())
3881            .map(|a| {
3882                a.iter()
3883                    .filter_map(|v| v.as_str().map(String::from))
3884                    .collect()
3885            })
3886            .unwrap_or_default();
3887        let max_r = args
3888            .get("max_results")
3889            .and_then(|v| v.as_u64())
3890            .unwrap_or(5) as usize;
3891        let kws: Vec<String> = problem
3892            .to_lowercase()
3893            .split_whitespace()
3894            .filter(|w| w.len() > 3)
3895            .map(String::from)
3896            .collect();
3897        let mut sols: Vec<Value> = Vec::new();
3898        for (gn, graph) in &self.graphs {
3899            for unit in graph.units() {
3900                if sols.len() >= max_r {
3901                    break;
3902                }
3903                if !languages.is_empty()
3904                    && !languages
3905                        .iter()
3906                        .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3907                {
3908                    continue;
3909                }
3910                let nl = unit.name.to_lowercase();
3911                let dl = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
3912                let mc = kws
3913                    .iter()
3914                    .filter(|kw| nl.contains(kw.as_str()) || dl.contains(kw.as_str()))
3915                    .count();
3916                if mc > 0 {
3917                    let rel = mc as f64 / kws.len().max(1) as f64;
3918                    sols.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "language": unit.language.name(), "file": unit.file_path.display().to_string(), "relevance": rel, "doc": unit.doc_summary, "signature": unit.signature}));
3919                }
3920            }
3921        }
3922        sols.sort_by(|a, b| {
3923            b["relevance"]
3924                .as_f64()
3925                .unwrap_or(0.0)
3926                .partial_cmp(&a["relevance"].as_f64().unwrap_or(0.0))
3927                .unwrap_or(std::cmp::Ordering::Equal)
3928        });
3929        sols.truncate(max_r);
3930        JsonRpcResponse::success(
3931            id,
3932            json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"problem": problem, "count": sols.len(), "solutions": sols})).unwrap_or_default()}]}),
3933        )
3934    }
3935}
3936
3937/// Truncate a JSON value to a short summary string.
3938fn truncate_json_summary(value: &Value, max_len: usize) -> String {
3939    let s = value.to_string();
3940    if s.len() <= max_len {
3941        s
3942    } else {
3943        format!("{}...", &s[..max_len])
3944    }
3945}
3946
3947impl Default for McpServer {
3948    fn default() -> Self {
3949        Self::new()
3950    }
3951}