1#![recursion_limit = "256"]
2pub mod brain;
16pub mod config;
17pub mod ffi;
18pub mod hanzo_api;
19pub mod server;
20pub mod protocol;
21pub mod tools;
22pub mod search;
23
24pub use config::Config;
25pub use server::MCPServer;
26pub use tools::{
27 ExecTool, FsTool, PlanTool, ThinkTool, MemoryTool,
28 ComputerTool, BrowserTool, ModeTool,
29 CodeTool, GitTool, FetchTool, WorkspaceTool, TasksTool, HanzoTool,
30 list_tools, parity_status,
31};
32
33use anyhow::Result;
34use serde::{Deserialize, Serialize};
35use serde_json::{json, Value};
36use std::collections::HashMap;
37use std::sync::Arc;
38use tokio::sync::RwLock;
39
40#[async_trait::async_trait]
42pub trait MCPTool: Send + Sync {
43 fn name(&self) -> &str;
45
46 fn description(&self) -> &str;
48
49 fn parameters(&self) -> serde_json::Value;
51
52 async fn execute(&self, params: serde_json::Value) -> Result<ToolResult>;
54}
55
56#[derive(Debug, Serialize, Deserialize)]
58pub struct ToolResult {
59 pub success: bool,
60 pub content: serde_json::Value,
61 pub error: Option<String>,
62}
63
64impl ToolResult {
65 pub fn ok(content: Value) -> Self {
66 Self {
67 success: true,
68 content,
69 error: None,
70 }
71 }
72
73 pub fn err(message: &str) -> Self {
74 Self {
75 success: false,
76 content: json!(null),
77 error: Some(message.to_string()),
78 }
79 }
80}
81
82pub struct ToolWrapper<T> {
84 pub tool: Arc<RwLock<T>>,
85 pub name: String,
86 pub description: String,
87 pub schema: Value,
88}
89
90pub struct ToolRegistry {
92 tools: HashMap<String, Box<dyn MCPTool>>,
93 exec: Arc<RwLock<ExecTool>>,
94 fs: Arc<RwLock<FsTool>>,
95 code: Arc<RwLock<CodeTool>>,
96 git: Arc<RwLock<GitTool>>,
97 fetch: Arc<RwLock<FetchTool>>,
98 workspace: Arc<RwLock<WorkspaceTool>>,
99 plan: Arc<RwLock<PlanTool>>,
100 think: Arc<RwLock<ThinkTool>>,
101 memory: Arc<RwLock<MemoryTool>>,
102 computer: Arc<RwLock<ComputerTool>>,
103 browser: Arc<RwLock<BrowserTool>>,
104 mode: Arc<RwLock<ModeTool>>,
105 tasks: Arc<RwLock<TasksTool>>,
106 hanzo: Arc<RwLock<HanzoTool>>,
107}
108
109impl ToolRegistry {
110 pub fn new() -> Self {
111 Self {
112 tools: HashMap::new(),
113 exec: Arc::new(RwLock::new(ExecTool::new())),
114 fs: Arc::new(RwLock::new(FsTool::new())),
115 code: Arc::new(RwLock::new(CodeTool::new())),
116 git: Arc::new(RwLock::new(GitTool::new())),
117 fetch: Arc::new(RwLock::new(FetchTool::new())),
118 workspace: Arc::new(RwLock::new(WorkspaceTool::new())),
119 plan: Arc::new(RwLock::new(PlanTool::new())),
120 think: Arc::new(RwLock::new(ThinkTool::new())),
121 memory: Arc::new(RwLock::new(MemoryTool::new())),
122 computer: Arc::new(RwLock::new(ComputerTool::new())),
123 browser: Arc::new(RwLock::new(BrowserTool::new())),
124 mode: Arc::new(RwLock::new(ModeTool::new())),
125 tasks: Arc::new(RwLock::new(TasksTool::new())),
126 hanzo: Arc::new(RwLock::new(HanzoTool::new())),
127 }
128 }
129
130 pub fn register(&mut self, tool: Box<dyn MCPTool>) {
131 self.tools.insert(tool.name().to_string(), tool);
132 }
133
134 pub fn get(&self, name: &str) -> Option<&Box<dyn MCPTool>> {
135 self.tools.get(name)
136 }
137
138 pub fn list(&self) -> Vec<String> {
139 let mut names: Vec<String> = self.tools.keys().cloned().collect();
140 names.extend(vec![
142 "exec".into(), "fs".into(), "code".into(), "git".into(),
143 "fetch".into(), "workspace".into(), "computer".into(),
144 "think".into(), "memory".into(), "hanzo".into(),
145 "plan".into(), "tasks".into(), "mode".into(),
146 "search".into(), "browser".into(),
147 ]);
148 names.sort();
149 names.dedup();
150 names
151 }
152
153 pub async fn execute(&self, name: &str, params: Value) -> Result<ToolResult> {
155 match name {
156 "exec" => {
157 let args: tools::ExecToolArgs = serde_json::from_value(params)?;
158 let result = self.exec.read().await.execute(args).await?;
159 Ok(ToolResult::ok(serde_json::from_str(&result)?))
160 }
161 "fs" => {
162 let args: tools::FsToolArgs = serde_json::from_value(params)?;
163 let result = self.fs.read().await.execute(args).await?;
164 Ok(ToolResult::ok(serde_json::from_str(&result)?))
165 }
166 "search" => {
167 let mut args: tools::FsToolArgs = serde_json::from_value(params)?;
168 if args.action.is_empty() {
169 args.action = "search".to_string();
170 }
171 let result = self.fs.read().await.execute(args).await?;
172 Ok(ToolResult::ok(serde_json::from_str(&result)?))
173 }
174 "plan" => {
175 let args: tools::PlanToolArgs = serde_json::from_value(params)?;
176 let result = self.plan.read().await.execute(args).await?;
177 Ok(ToolResult::ok(serde_json::from_str(&result)?))
178 }
179 "think" => {
180 let args: tools::ThinkToolArgs = serde_json::from_value(params)?;
181 let result = self.think.read().await.execute(args).await?;
182 Ok(ToolResult::ok(result))
183 }
184 "memory" => {
185 let args: tools::MemoryToolArgs = serde_json::from_value(params)?;
186 let result = self.memory.read().await.execute(args).await?;
187 Ok(ToolResult::ok(serde_json::from_str(&result)?))
188 }
189 "computer" => {
190 let args: tools::ComputerToolArgs = serde_json::from_value(params)?;
191 let mut computer = self.computer.write().await;
192 let result = computer.execute(args).await?;
193 Ok(ToolResult::ok(serde_json::from_str(&result)?))
194 }
195 "browser" => {
196 let args: tools::BrowserToolArgs = serde_json::from_value(params)?;
197 let result = self.browser.read().await.execute(args).await?;
198 Ok(ToolResult::ok(serde_json::from_str(&result)?))
199 }
200 "mode" => {
201 let args: tools::ModeToolArgs = serde_json::from_value(params)?;
202 let result = self.mode.read().await.execute(args).await?;
203 Ok(ToolResult::ok(serde_json::from_str(&result)?))
204 }
205 "code" => {
206 let args: tools::CodeToolArgs = serde_json::from_value(params)?;
207 let result = self.code.read().await.execute(args).await?;
208 Ok(ToolResult::ok(result))
209 }
210 "git" => {
211 let args: tools::GitToolArgs = serde_json::from_value(params)?;
212 let result = self.git.read().await.execute(args).await?;
213 Ok(ToolResult::ok(result))
214 }
215 "fetch" => {
216 let args: tools::FetchToolArgs = serde_json::from_value(params)?;
217 let result = self.fetch.read().await.execute(args).await?;
218 Ok(ToolResult::ok(result))
219 }
220 "workspace" => {
221 let args: tools::WorkspaceToolArgs = serde_json::from_value(params)?;
222 let result = self.workspace.read().await.execute(args).await?;
223 Ok(ToolResult::ok(result))
224 }
225 "tasks" => {
226 let args: tools::TasksToolArgs = serde_json::from_value(params)?;
227 let result = self.tasks.read().await.execute(args).await?;
228 Ok(ToolResult::ok(result))
229 }
230 "hanzo" => {
231 let args: tools::HanzoToolArgs = serde_json::from_value(params)?;
232 let result = self.hanzo.read().await.execute(args).await?;
233 Ok(ToolResult::ok(result))
234 }
235 _ => {
236 if let Some(tool) = self.tools.get(name) {
237 tool.execute(params).await
238 } else {
239 Ok(ToolResult::err(&format!("Unknown tool: {}", name)))
240 }
241 }
242 }
243 }
244
245 pub fn get_definitions(&self) -> Vec<Value> {
247 let mut definitions = vec![
248 json!({
249 "name": "exec",
250 "description": tools::ExecToolDefinition::new().description,
251 "inputSchema": tools::ExecToolDefinition::new().input_schema
252 }),
253 json!({
254 "name": "fs",
255 "description": tools::FsToolDefinition::new().description,
256 "inputSchema": tools::FsToolDefinition::new().input_schema
257 }),
258 json!({
259 "name": "search",
260 "description": "Search file contents (alias of fs with action=search)",
261 "inputSchema": tools::FsToolDefinition::new().input_schema
262 }),
263 json!({
264 "name": "plan",
265 "description": tools::PlanToolDefinition::new().description,
266 "inputSchema": tools::PlanToolDefinition::new().input_schema
267 }),
268 json!({
269 "name": "think",
270 "description": tools::ThinkToolDefinition::new().description,
271 "inputSchema": tools::ThinkToolDefinition::new().input_schema
272 }),
273 json!({
274 "name": "memory",
275 "description": tools::MemoryToolDefinition::new().description,
276 "inputSchema": tools::MemoryToolDefinition::new().input_schema
277 }),
278 json!({
279 "name": "computer",
280 "description": tools::ComputerToolDefinition::new().description,
281 "inputSchema": tools::ComputerToolDefinition::new().input_schema
282 }),
283 json!({
284 "name": "browser",
285 "description": tools::BrowserToolDefinition::new().description,
286 "inputSchema": tools::BrowserToolDefinition::new().input_schema
287 }),
288 json!({
289 "name": "mode",
290 "description": tools::ModeToolDefinition::new().description,
291 "inputSchema": tools::ModeToolDefinition::new().input_schema
292 }),
293 tools::CodeToolDefinition::schema(),
294 tools::GitToolDefinition::schema(),
295 tools::FetchToolDefinition::schema(),
296 tools::WorkspaceToolDefinition::schema(),
297 tools::TasksToolDefinition::schema(),
298 tools::HanzoToolDefinition::schema(),
299 ];
300
301 for tool in self.tools.values() {
303 definitions.push(json!({
304 "name": tool.name(),
305 "description": tool.description(),
306 "inputSchema": tool.parameters()
307 }));
308 }
309
310 definitions
311 }
312
313 pub fn with_defaults() -> Self {
315 let mut registry = Self::new();
316
317 registry.register(Box::new(tools::CodeSearchTool::new()));
321 registry.register(Box::new(tools::CodeContextTool::new()));
322 registry.register(Box::new(tools::CodeAskTool::new()));
323 registry.register(Box::new(tools::CodeIndexTool::new()));
324 registry.register(Box::new(tools::WebSearchTool::new()));
325 registry.register(Box::new(tools::WebReadTool::new()));
326 registry.register(Box::new(tools::ResearchTool::new()));
327 registry.register(Box::new(tools::VisionTool::new()));
328
329 registry.register(Box::new(tools::ConfigTool::new()));
332 registry.register(Box::new(tools::LlmTool::new()));
333 registry.register(Box::new(tools::UiTool::new()));
334 registry.register(Box::new(tools::AgentTool::new()));
335 registry.register(Box::new(tools::LspTool::new()));
336 registry.register(Box::new(tools::RefactorTool::new()));
337 registry.register(Box::new(tools::SystemTool::new()));
338
339 #[cfg(feature = "computer-control")]
340 {
341 }
343
344 #[cfg(feature = "vector-store")]
345 {
346 }
348
349 registry
350 }
351}
352
353impl Default for ToolRegistry {
354 fn default() -> Self {
355 Self::with_defaults()
356 }
357}
358
359pub fn version() -> Value {
361 json!({
362 "name": "hanzo-mcp",
363 "version": env!("CARGO_PKG_VERSION"),
364 "rust_version": "1.75+",
365 "tools": list_tools().len(),
366 "parity": parity_status()
367 })
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373
374 #[test]
375 fn test_tool_registry() {
376 let registry = ToolRegistry::new();
377 let tools = registry.list();
378 assert!(tools.contains(&"exec".to_string()));
379 assert!(tools.contains(&"fs".to_string()));
380 assert!(tools.contains(&"search".to_string()));
381 assert!(tools.contains(&"plan".to_string()));
382 assert!(tools.contains(&"think".to_string()));
383 assert!(tools.contains(&"memory".to_string()));
384 assert!(tools.contains(&"computer".to_string()));
385 assert!(tools.contains(&"browser".to_string()));
386 assert!(tools.contains(&"mode".to_string()));
387 }
388
389 #[test]
390 fn test_tool_definitions() {
391 let registry = ToolRegistry::new();
392 let definitions = registry.get_definitions();
393 assert!(definitions.len() >= 9);
394 }
395
396 #[test]
397 fn test_cloud_tools_registered_and_discoverable() {
398 let registry = ToolRegistry::with_defaults();
399 let names = registry.list();
400 for t in ["code_search", "code_context", "code_ask", "code_index", "web_search", "web_read", "research", "vision"] {
401 assert!(names.contains(&t.to_string()), "{t} missing from registry.list()");
402 }
403 let defs = registry.get_definitions();
405 for t in ["code_search", "code_context", "code_ask", "code_index", "web_search", "web_read", "research", "vision"] {
406 assert!(
407 defs.iter().any(|d| d["name"] == t && d["inputSchema"].is_object()),
408 "{t} missing a definition"
409 );
410 }
411 }
412
413 #[tokio::test]
414 async fn test_proc_execute() {
415 let registry = ToolRegistry::new();
416 let result = registry.execute("exec", json!({
417 "action": "help"
418 })).await;
419 assert!(result.is_ok());
420 }
421
422 #[tokio::test]
423 async fn test_fs_execute() {
424 let registry = ToolRegistry::new();
425 let result = registry.execute("fs", json!({
426 "action": "help"
427 })).await;
428 assert!(result.is_ok());
429 }
430
431 #[test]
432 fn test_version() {
433 let v = version();
434 assert!(v.get("name").is_some());
435 assert!(v.get("version").is_some());
436 assert!(v.get("tools").is_some());
437 }
438}