Skip to main content

lean_ctx/server/
registry.rs

1use std::collections::HashMap;
2
3use rmcp::model::Tool;
4
5use super::tool_trait::McpTool;
6
7/// Central registry mapping tool names to their trait-based handlers.
8/// Replaces the match-cascade dispatch for migrated tools while
9/// coexisting with the legacy dispatch for tools not yet migrated.
10pub struct ToolRegistry {
11    tools: HashMap<&'static str, Box<dyn McpTool>>,
12}
13
14impl ToolRegistry {
15    pub fn new() -> Self {
16        Self {
17            tools: HashMap::new(),
18        }
19    }
20
21    pub fn register(&mut self, tool: Box<dyn McpTool>) {
22        self.tools.insert(tool.name(), tool);
23    }
24
25    pub fn get(&self, name: &str) -> Option<&dyn McpTool> {
26        self.tools.get(name).map(AsRef::as_ref)
27    }
28
29    pub fn contains(&self, name: &str) -> bool {
30        self.tools.contains_key(name)
31    }
32
33    /// Returns MCP Tool definitions for all registered tools.
34    /// Used by `list_tools` to expose schemas to clients.
35    pub fn tool_defs(&self) -> Vec<Tool> {
36        let mut defs: Vec<Tool> = self.tools.values().map(|t| t.tool_def()).collect();
37        defs.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
38        defs
39    }
40
41    /// Returns tool definitions filtered by the dynamic tool state.
42    /// Only includes tools whose category is currently active.
43    pub fn active_tool_defs(&self) -> Vec<Tool> {
44        let Ok(state) = super::dynamic_tools::global().lock() else {
45            tracing::warn!("dynamic_tools mutex poisoned in active_tool_defs; returning all");
46            return self.tool_defs();
47        };
48        let mut defs: Vec<Tool> = self
49            .tools
50            .values()
51            .filter(|t| state.is_tool_active(t.name()))
52            .map(|t| t.tool_def())
53            .collect();
54        defs.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
55        defs
56    }
57
58    pub fn len(&self) -> usize {
59        self.tools.len()
60    }
61
62    pub fn is_empty(&self) -> bool {
63        self.tools.is_empty()
64    }
65
66    pub fn names(&self) -> Vec<&'static str> {
67        let mut names: Vec<_> = self.tools.keys().copied().collect();
68        names.sort_unstable();
69        names
70    }
71}
72
73impl Default for ToolRegistry {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79/// Number of registered MCP tools — the single source of truth for the
80/// "N MCP tools" count shown in `--help`, the README, and the feature catalog.
81/// Deriving it here means the count can never drift from the actual registry.
82pub fn tool_count() -> usize {
83    build_registry().len()
84}
85
86/// Register all trait-based tools. Called once during server startup.
87/// Tools are added here as they are migrated from the legacy dispatch.
88pub fn build_registry() -> ToolRegistry {
89    let mut registry = ToolRegistry::new();
90
91    use crate::tools::registered;
92    registry.register(Box::new(registered::ctx_tree::CtxTreeTool));
93    registry.register(Box::new(registered::ctx_benchmark::CtxBenchmarkTool));
94    registry.register(Box::new(registered::ctx_analyze::CtxAnalyzeTool));
95    registry.register(Box::new(registered::ctx_discover::CtxDiscoverTool));
96    registry.register(Box::new(registered::ctx_response::CtxResponseTool));
97    registry.register(Box::new(registered::ctx_heatmap::CtxHeatmapTool));
98    registry.register(Box::new(registered::ctx_verify::CtxVerifyTool));
99    registry.register(Box::new(registered::ctx_outline::CtxOutlineTool));
100    registry.register(Box::new(registered::ctx_cost::CtxCostTool));
101    registry.register(Box::new(registered::ctx_gain::CtxGainTool));
102    registry.register(Box::new(registered::ctx_expand::CtxExpandTool));
103    registry.register(Box::new(registered::ctx_routes::CtxRoutesTool));
104    registry.register(Box::new(registered::ctx_call::CtxCallTool));
105    registry.register(Box::new(registered::ctx_callgraph::CtxCallgraphTool));
106    registry.register(Box::new(registered::ctx_refactor::CtxRefactorTool));
107    registry.register(Box::new(registered::ctx_symbol::CtxSymbolTool));
108    registry.register(Box::new(
109        registered::ctx_discover_tools::CtxDiscoverToolsTool,
110    ));
111    registry.register(Box::new(registered::ctx_review::CtxReviewTool));
112    registry.register(Box::new(registered::ctx_provider::CtxProviderTool));
113    registry.register(Box::new(registered::ctx_impact::CtxImpactTool));
114    registry.register(Box::new(registered::ctx_architecture::CtxArchitectureTool));
115    registry.register(Box::new(registered::ctx_smells::CtxSmellsTool));
116    registry.register(Box::new(registered::ctx_pack::CtxPackTool));
117    registry.register(Box::new(registered::ctx_index::CtxIndexTool));
118    registry.register(Box::new(registered::ctx_artifacts::CtxArtifactsTool));
119    registry.register(Box::new(
120        registered::ctx_compress_memory::CtxCompressMemoryTool,
121    ));
122    registry.register(Box::new(registered::ctx_read::CtxReadTool));
123    registry.register(Box::new(registered::ctx_multi_read::CtxMultiReadTool));
124    registry.register(Box::new(registered::ctx_smart_read::CtxSmartReadTool));
125    registry.register(Box::new(registered::ctx_delta::CtxDeltaTool));
126    registry.register(Box::new(registered::ctx_edit::CtxEditTool));
127    registry.register(Box::new(registered::ctx_fill::CtxFillTool));
128    registry.register(Box::new(registered::ctx_shell::CtxShellTool));
129    registry.register(Box::new(registered::ctx_search::CtxSearchTool));
130    registry.register(Box::new(registered::ctx_compose::CtxComposeTool));
131    registry.register(Box::new(registered::ctx_execute::CtxExecuteTool));
132
133    // Utility tools (migrated from dispatch/utility_tools.rs)
134    registry.register(Box::new(registered::ctx_compress::CtxCompressTool));
135    registry.register(Box::new(registered::ctx_metrics::CtxMetricsTool));
136    registry.register(Box::new(registered::ctx_radar::CtxRadarTool));
137    registry.register(Box::new(registered::ctx_dedup::CtxDedupTool));
138    registry.register(Box::new(registered::ctx_intent::CtxIntentTool));
139    registry.register(Box::new(registered::ctx_context::CtxContextTool));
140    registry.register(Box::new(registered::ctx_graph::CtxGraphTool));
141    registry.register(Box::new(registered::ctx_proof::CtxProofTool));
142    registry.register(Box::new(registered::ctx_cache::CtxCacheTool));
143    registry.register(Box::new(registered::ctx_ledger::CtxLedgerTool));
144    registry.register(Box::new(registered::ctx_retrieve::CtxRetrieveTool));
145    registry.register(Box::new(registered::ctx_overview::CtxOverviewTool));
146    registry.register(Box::new(registered::ctx_preload::CtxPreloadTool));
147    registry.register(Box::new(registered::ctx_prefetch::CtxPrefetchTool));
148    registry.register(Box::new(
149        registered::ctx_semantic_search::CtxSemanticSearchTool,
150    ));
151    registry.register(Box::new(registered::ctx_feedback::CtxFeedbackTool));
152    registry.register(Box::new(registered::ctx_control::CtxControlTool));
153    registry.register(Box::new(registered::ctx_plan::CtxPlanTool));
154    registry.register(Box::new(registered::ctx_compile::CtxCompileTool));
155
156    // Session tools (migrated from legacy dispatch)
157    registry.register(Box::new(registered::ctx_session::CtxSessionTool));
158    registry.register(Box::new(registered::ctx_knowledge::CtxKnowledgeTool));
159    registry.register(Box::new(registered::ctx_agent::CtxAgentTool));
160    registry.register(Box::new(registered::ctx_share::CtxShareTool));
161    registry.register(Box::new(registered::ctx_task::CtxTaskTool));
162    registry.register(Box::new(registered::ctx_handoff::CtxHandoffTool));
163    registry.register(Box::new(registered::ctx_workflow::CtxWorkflowTool));
164    registry.register(Box::new(registered::ctx_load_tools::CtxLoadToolsTool));
165
166    registry
167}