lean_ctx/server/
registry.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use rmcp::model::Tool;
5
6use super::tool_trait::McpTool;
7
8pub struct ToolRegistry {
18 tools: HashMap<&'static str, Arc<dyn McpTool>>,
19}
20
21impl ToolRegistry {
22 pub fn new() -> Self {
23 Self {
24 tools: HashMap::new(),
25 }
26 }
27
28 pub fn register(&mut self, tool: Box<dyn McpTool>) {
29 let name = tool.name();
30 self.tools.insert(name, Arc::from(tool));
31 }
32
33 pub fn get(&self, name: &str) -> Option<&dyn McpTool> {
34 self.tools.get(name).map(|t| &**t)
35 }
36
37 pub fn get_arc(&self, name: &str) -> Option<Arc<dyn McpTool>> {
43 self.tools.get(name).cloned()
44 }
45
46 pub fn contains(&self, name: &str) -> bool {
47 self.tools.contains_key(name)
48 }
49
50 pub fn tool_defs(&self) -> Vec<Tool> {
55 let mut defs: Vec<Tool> = self.tools.values().map(|t| t.tool_def()).collect();
56 defs.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
57 crate::tool_defs::apply_tool_annotations(defs)
58 }
59
60 pub fn active_tool_defs(&self) -> Vec<Tool> {
63 let Ok(state) = super::dynamic_tools::global().lock() else {
64 tracing::warn!("dynamic_tools mutex poisoned in active_tool_defs; returning all");
65 return self.tool_defs();
66 };
67 let mut defs: Vec<Tool> = self
68 .tools
69 .values()
70 .filter(|t| state.is_tool_active(t.name()))
71 .map(|t| t.tool_def())
72 .collect();
73 defs.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
74 crate::tool_defs::apply_tool_annotations(defs)
75 }
76
77 pub fn profile_tool_defs(
80 &self,
81 profile: &crate::core::tool_profiles::ToolProfile,
82 ) -> Vec<Tool> {
83 let mut defs: Vec<Tool> = self
84 .tools
85 .values()
86 .filter(|t| profile.is_tool_enabled(t.name()))
87 .map(|t| t.tool_def())
88 .collect();
89 defs.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
90 crate::tool_defs::apply_tool_annotations(defs)
91 }
92
93 pub fn len(&self) -> usize {
94 self.tools.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
98 self.tools.is_empty()
99 }
100
101 pub fn names(&self) -> Vec<&'static str> {
102 let mut names: Vec<_> = self.tools.keys().copied().collect();
103 names.sort_unstable();
104 names
105 }
106}
107
108impl Default for ToolRegistry {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114pub fn tool_count() -> usize {
118 build_registry().len()
119}
120
121pub fn build_registry() -> ToolRegistry {
124 let mut registry = ToolRegistry::new();
125
126 use crate::tools::registered;
127 registry.register(Box::new(registered::ctx_tree::CtxTreeTool));
128 registry.register(Box::new(registered::ctx_benchmark::CtxBenchmarkTool));
129 registry.register(Box::new(registered::ctx_quality_lab::CtxQualityLabTool));
130 registry.register(Box::new(registered::ctx_analyze::CtxAnalyzeTool));
131 registry.register(Box::new(registered::ctx_discover::CtxDiscoverTool));
132 registry.register(Box::new(registered::ctx_response::CtxResponseTool));
133 registry.register(Box::new(registered::ctx_heatmap::CtxHeatmapTool));
134 registry.register(Box::new(registered::ctx_verify::CtxVerifyTool));
135 registry.register(Box::new(registered::ctx_outline::CtxOutlineTool));
136 registry.register(Box::new(registered::ctx_cost::CtxCostTool));
137 registry.register(Box::new(registered::ctx_gain::CtxGainTool));
138 registry.register(Box::new(registered::ctx_expand::CtxExpandTool));
139 registry.register(Box::new(registered::ctx_routes::CtxRoutesTool));
140 registry.register(Box::new(registered::ctx_call::CtxCallTool));
141 registry.register(Box::new(registered::ctx_callgraph::CtxCallgraphTool));
142 registry.register(Box::new(registered::ctx_refactor::CtxRefactorTool));
143 registry.register(Box::new(registered::ctx_repomap::CtxRepomapTool));
144 registry.register(Box::new(registered::ctx_symbol::CtxSymbolTool));
145 registry.register(Box::new(
146 registered::ctx_discover_tools::CtxDiscoverToolsTool,
147 ));
148 registry.register(Box::new(registered::ctx_tools::CtxToolsTool));
149 registry.register(Box::new(registered::ctx_review::CtxReviewTool));
150 registry.register(Box::new(registered::ctx_provider::CtxProviderTool));
151 registry.register(Box::new(registered::ctx_impact::CtxImpactTool));
152 registry.register(Box::new(registered::ctx_architecture::CtxArchitectureTool));
153 registry.register(Box::new(registered::ctx_smells::CtxSmellsTool));
154 registry.register(Box::new(registered::ctx_quality::CtxQualityTool));
155 registry.register(Box::new(registered::ctx_pack::CtxPackTool));
156 registry.register(Box::new(registered::ctx_plugins::CtxPluginsTool));
157 registry.register(Box::new(registered::ctx_rules::CtxRulesTool));
158 registry.register(Box::new(registered::ctx_index::CtxIndexTool));
159 registry.register(Box::new(registered::ctx_artifacts::CtxArtifactsTool));
160 registry.register(Box::new(
161 registered::ctx_compress_memory::CtxCompressMemoryTool,
162 ));
163 registry.register(Box::new(registered::ctx_read::CtxReadTool));
164 registry.register(Box::new(registered::ctx_multi_read::CtxMultiReadTool));
165 registry.register(Box::new(registered::ctx_multi_repo::CtxMultiRepoTool));
166 registry.register(Box::new(registered::ctx_smart_read::CtxSmartReadTool));
167 registry.register(Box::new(registered::ctx_delta::CtxDeltaTool));
168 registry.register(Box::new(registered::ctx_edit::CtxEditTool));
169 registry.register(Box::new(registered::ctx_patch::CtxPatchTool));
170 registry.register(Box::new(registered::ctx_fill::CtxFillTool));
171 registry.register(Box::new(registered::ctx_glob::CtxGlobTool));
172 registry.register(Box::new(registered::ctx_shell::CtxShellTool));
173 registry.register(Box::new(registered::shell_alias::ShellAliasTool));
174 registry.register(Box::new(registered::ctx_search::CtxSearchTool));
175 registry.register(Box::new(registered::ctx_url_read::CtxUrlReadTool));
176 registry.register(Box::new(registered::ctx_git_read::CtxGitReadTool));
177 registry.register(Box::new(registered::ctx_checkpoint::CtxCheckpointTool));
178 registry.register(Box::new(registered::ctx_compose::CtxComposeTool));
179 registry.register(Box::new(registered::ctx_explore::CtxExploreTool));
180 registry.register(Box::new(registered::ctx_execute::CtxExecuteTool));
181
182 registry.register(Box::new(registered::ctx_compress::CtxCompressTool));
184 registry.register(Box::new(registered::ctx_compare::CtxCompareTool));
185 registry.register(Box::new(crate::tools::ctx_cognitive::CtxCognitiveTool));
186 registry.register(Box::new(registered::ctx_metrics::CtxMetricsTool));
187 registry.register(Box::new(registered::ctx_radar::CtxRadarTool));
188 registry.register(Box::new(registered::ctx_dedup::CtxDedupTool));
189 registry.register(Box::new(registered::ctx_intent::CtxIntentTool));
190 registry.register(Box::new(registered::ctx_context::CtxContextTool));
191 registry.register(Box::new(registered::ctx_graph::CtxGraphTool));
192 registry.register(Box::new(registered::ctx_proof::CtxProofTool));
193 registry.register(Box::new(registered::ctx_cache::CtxCacheTool));
194 registry.register(Box::new(registered::ctx_ledger::CtxLedgerTool));
195 registry.register(Box::new(registered::ctx_retrieve::CtxRetrieveTool));
196 registry.register(Box::new(registered::ctx_overview::CtxOverviewTool));
197 registry.register(Box::new(registered::ctx_preload::CtxPreloadTool));
198 registry.register(Box::new(registered::ctx_prefetch::CtxPrefetchTool));
199 registry.register(Box::new(
200 registered::ctx_semantic_search::CtxSemanticSearchTool,
201 ));
202 registry.register(Box::new(registered::ctx_feedback::CtxFeedbackTool));
203 registry.register(Box::new(registered::ctx_control::CtxControlTool));
204 registry.register(Box::new(registered::ctx_plan::CtxPlanTool));
205 registry.register(Box::new(registered::ctx_compile::CtxCompileTool));
206
207 registry.register(Box::new(registered::ctx_session::CtxSessionTool));
209 registry.register(Box::new(registered::ctx_knowledge::CtxKnowledgeTool));
210 registry.register(Box::new(registered::ctx_agent::CtxAgentTool));
211 registry.register(Box::new(registered::ctx_share::CtxShareTool));
212 registry.register(Box::new(registered::ctx_skillify::CtxSkillifyTool));
213 registry.register(Box::new(registered::ctx_summary::CtxSummaryTool));
214 registry.register(Box::new(
215 registered::ctx_transcript_compact::CtxTranscriptCompactTool,
216 ));
217 registry.register(Box::new(registered::ctx_package::CtxPackageTool));
218 registry.register(Box::new(registered::ctx_task::CtxTaskTool));
219 registry.register(Box::new(registered::ctx_handoff::CtxHandoffTool));
220 registry.register(Box::new(registered::ctx_workflow::CtxWorkflowTool));
221 registry.register(Box::new(registered::ctx_load_tools::CtxLoadToolsTool));
222
223 register_plugin_tools(&mut registry);
224
225 registry
226}
227
228fn register_plugin_tools(registry: &mut ToolRegistry) {
233 for spec in crate::core::plugins::PluginManager::tool_specs() {
234 if registry.contains(&spec.name) {
235 tracing::warn!(
236 "plugin '{}' tool '{}' collides with a native tool; skipping",
237 spec.plugin_name,
238 spec.name
239 );
240 continue;
241 }
242 registry.register(Box::new(
243 crate::tools::registered::plugin_tool::PluginTool::from_spec(spec),
244 ));
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn get_arc_returns_owned_handle_for_known_tool() {
254 let registry = build_registry();
255 let arc = registry.get_arc("ctx_tree");
256 assert!(arc.is_some(), "ctx_tree must be registered");
257 assert_eq!(arc.unwrap().name(), "ctx_tree");
258 }
259
260 #[test]
261 fn get_arc_is_none_for_unknown_tool() {
262 let registry = build_registry();
263 assert!(registry.get_arc("ctx_does_not_exist_xyz").is_none());
264 }
265
266 #[test]
267 fn get_and_get_arc_agree_for_core_tool() {
268 let registry = build_registry();
269 assert_eq!(
270 registry.get("ctx_read").is_some(),
271 registry.get_arc("ctx_read").is_some(),
272 "get and get_arc must agree on tool presence"
273 );
274 }
275}