#![recursion_limit = "256"]
pub mod brain;
pub mod config;
pub mod ffi;
pub mod hanzo_api;
pub mod server;
pub mod protocol;
pub mod tools;
pub mod search;
pub use config::Config;
pub use server::MCPServer;
pub use tools::{
ExecTool, FsTool, PlanTool, ThinkTool, MemoryTool,
ComputerTool, BrowserTool, ModeTool,
CodeTool, GitTool, FetchTool, WorkspaceTool, TasksTool, HanzoTool,
list_tools, parity_status,
};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[async_trait::async_trait]
pub trait MCPTool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> serde_json::Value;
async fn execute(&self, params: serde_json::Value) -> Result<ToolResult>;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ToolResult {
pub success: bool,
pub content: serde_json::Value,
pub error: Option<String>,
}
impl ToolResult {
pub fn ok(content: Value) -> Self {
Self {
success: true,
content,
error: None,
}
}
pub fn err(message: &str) -> Self {
Self {
success: false,
content: json!(null),
error: Some(message.to_string()),
}
}
}
pub struct ToolWrapper<T> {
pub tool: Arc<RwLock<T>>,
pub name: String,
pub description: String,
pub schema: Value,
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn MCPTool>>,
exec: Arc<RwLock<ExecTool>>,
fs: Arc<RwLock<FsTool>>,
code: Arc<RwLock<CodeTool>>,
git: Arc<RwLock<GitTool>>,
fetch: Arc<RwLock<FetchTool>>,
workspace: Arc<RwLock<WorkspaceTool>>,
plan: Arc<RwLock<PlanTool>>,
think: Arc<RwLock<ThinkTool>>,
memory: Arc<RwLock<MemoryTool>>,
computer: Arc<RwLock<ComputerTool>>,
browser: Arc<RwLock<BrowserTool>>,
mode: Arc<RwLock<ModeTool>>,
tasks: Arc<RwLock<TasksTool>>,
hanzo: Arc<RwLock<HanzoTool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
exec: Arc::new(RwLock::new(ExecTool::new())),
fs: Arc::new(RwLock::new(FsTool::new())),
code: Arc::new(RwLock::new(CodeTool::new())),
git: Arc::new(RwLock::new(GitTool::new())),
fetch: Arc::new(RwLock::new(FetchTool::new())),
workspace: Arc::new(RwLock::new(WorkspaceTool::new())),
plan: Arc::new(RwLock::new(PlanTool::new())),
think: Arc::new(RwLock::new(ThinkTool::new())),
memory: Arc::new(RwLock::new(MemoryTool::new())),
computer: Arc::new(RwLock::new(ComputerTool::new())),
browser: Arc::new(RwLock::new(BrowserTool::new())),
mode: Arc::new(RwLock::new(ModeTool::new())),
tasks: Arc::new(RwLock::new(TasksTool::new())),
hanzo: Arc::new(RwLock::new(HanzoTool::new())),
}
}
pub fn register(&mut self, tool: Box<dyn MCPTool>) {
self.tools.insert(tool.name().to_string(), tool);
}
pub fn get(&self, name: &str) -> Option<&Box<dyn MCPTool>> {
self.tools.get(name)
}
pub fn list(&self) -> Vec<String> {
let mut names: Vec<String> = self.tools.keys().cloned().collect();
names.extend(vec![
"exec".into(), "fs".into(), "code".into(), "git".into(),
"fetch".into(), "workspace".into(), "computer".into(),
"think".into(), "memory".into(), "hanzo".into(),
"plan".into(), "tasks".into(), "mode".into(),
"search".into(), "browser".into(),
]);
names.sort();
names.dedup();
names
}
pub async fn execute(&self, name: &str, params: Value) -> Result<ToolResult> {
match name {
"exec" => {
let args: tools::ExecToolArgs = serde_json::from_value(params)?;
let result = self.exec.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"fs" => {
let args: tools::FsToolArgs = serde_json::from_value(params)?;
let result = self.fs.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"search" => {
let mut args: tools::FsToolArgs = serde_json::from_value(params)?;
if args.action.is_empty() {
args.action = "search".to_string();
}
let result = self.fs.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"plan" => {
let args: tools::PlanToolArgs = serde_json::from_value(params)?;
let result = self.plan.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"think" => {
let args: tools::ThinkToolArgs = serde_json::from_value(params)?;
let result = self.think.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"memory" => {
let args: tools::MemoryToolArgs = serde_json::from_value(params)?;
let result = self.memory.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"computer" => {
let args: tools::ComputerToolArgs = serde_json::from_value(params)?;
let mut computer = self.computer.write().await;
let result = computer.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"browser" => {
let args: tools::BrowserToolArgs = serde_json::from_value(params)?;
let result = self.browser.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"mode" => {
let args: tools::ModeToolArgs = serde_json::from_value(params)?;
let result = self.mode.read().await.execute(args).await?;
Ok(ToolResult::ok(serde_json::from_str(&result)?))
}
"code" => {
let args: tools::CodeToolArgs = serde_json::from_value(params)?;
let result = self.code.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"git" => {
let args: tools::GitToolArgs = serde_json::from_value(params)?;
let result = self.git.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"fetch" => {
let args: tools::FetchToolArgs = serde_json::from_value(params)?;
let result = self.fetch.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"workspace" => {
let args: tools::WorkspaceToolArgs = serde_json::from_value(params)?;
let result = self.workspace.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"tasks" => {
let args: tools::TasksToolArgs = serde_json::from_value(params)?;
let result = self.tasks.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
"hanzo" => {
let args: tools::HanzoToolArgs = serde_json::from_value(params)?;
let result = self.hanzo.read().await.execute(args).await?;
Ok(ToolResult::ok(result))
}
_ => {
if let Some(tool) = self.tools.get(name) {
tool.execute(params).await
} else {
Ok(ToolResult::err(&format!("Unknown tool: {}", name)))
}
}
}
}
pub fn get_definitions(&self) -> Vec<Value> {
let mut definitions = vec![
json!({
"name": "exec",
"description": tools::ExecToolDefinition::new().description,
"inputSchema": tools::ExecToolDefinition::new().input_schema
}),
json!({
"name": "fs",
"description": tools::FsToolDefinition::new().description,
"inputSchema": tools::FsToolDefinition::new().input_schema
}),
json!({
"name": "search",
"description": "Search file contents (alias of fs with action=search)",
"inputSchema": tools::FsToolDefinition::new().input_schema
}),
json!({
"name": "plan",
"description": tools::PlanToolDefinition::new().description,
"inputSchema": tools::PlanToolDefinition::new().input_schema
}),
json!({
"name": "think",
"description": tools::ThinkToolDefinition::new().description,
"inputSchema": tools::ThinkToolDefinition::new().input_schema
}),
json!({
"name": "memory",
"description": tools::MemoryToolDefinition::new().description,
"inputSchema": tools::MemoryToolDefinition::new().input_schema
}),
json!({
"name": "computer",
"description": tools::ComputerToolDefinition::new().description,
"inputSchema": tools::ComputerToolDefinition::new().input_schema
}),
json!({
"name": "browser",
"description": tools::BrowserToolDefinition::new().description,
"inputSchema": tools::BrowserToolDefinition::new().input_schema
}),
json!({
"name": "mode",
"description": tools::ModeToolDefinition::new().description,
"inputSchema": tools::ModeToolDefinition::new().input_schema
}),
tools::CodeToolDefinition::schema(),
tools::GitToolDefinition::schema(),
tools::FetchToolDefinition::schema(),
tools::WorkspaceToolDefinition::schema(),
tools::TasksToolDefinition::schema(),
tools::HanzoToolDefinition::schema(),
];
for tool in self.tools.values() {
definitions.push(json!({
"name": tool.name(),
"description": tool.description(),
"inputSchema": tool.parameters()
}));
}
definitions
}
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.register(Box::new(tools::CodeSearchTool::new()));
registry.register(Box::new(tools::CodeContextTool::new()));
registry.register(Box::new(tools::CodeAskTool::new()));
registry.register(Box::new(tools::CodeIndexTool::new()));
registry.register(Box::new(tools::WebSearchTool::new()));
registry.register(Box::new(tools::WebReadTool::new()));
registry.register(Box::new(tools::ResearchTool::new()));
registry.register(Box::new(tools::VisionTool::new()));
registry.register(Box::new(tools::ConfigTool::new()));
registry.register(Box::new(tools::LlmTool::new()));
registry.register(Box::new(tools::UiTool::new()));
registry.register(Box::new(tools::AgentTool::new()));
registry.register(Box::new(tools::LspTool::new()));
registry.register(Box::new(tools::RefactorTool::new()));
registry.register(Box::new(tools::SystemTool::new()));
#[cfg(feature = "computer-control")]
{
}
#[cfg(feature = "vector-store")]
{
}
registry
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::with_defaults()
}
}
pub fn version() -> Value {
json!({
"name": "hanzo-mcp",
"version": env!("CARGO_PKG_VERSION"),
"rust_version": "1.75+",
"tools": list_tools().len(),
"parity": parity_status()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_registry() {
let registry = ToolRegistry::new();
let tools = registry.list();
assert!(tools.contains(&"exec".to_string()));
assert!(tools.contains(&"fs".to_string()));
assert!(tools.contains(&"search".to_string()));
assert!(tools.contains(&"plan".to_string()));
assert!(tools.contains(&"think".to_string()));
assert!(tools.contains(&"memory".to_string()));
assert!(tools.contains(&"computer".to_string()));
assert!(tools.contains(&"browser".to_string()));
assert!(tools.contains(&"mode".to_string()));
}
#[test]
fn test_tool_definitions() {
let registry = ToolRegistry::new();
let definitions = registry.get_definitions();
assert!(definitions.len() >= 9);
}
#[test]
fn test_cloud_tools_registered_and_discoverable() {
let registry = ToolRegistry::with_defaults();
let names = registry.list();
for t in ["code_search", "code_context", "code_ask", "code_index", "web_search", "web_read", "research", "vision"] {
assert!(names.contains(&t.to_string()), "{t} missing from registry.list()");
}
let defs = registry.get_definitions();
for t in ["code_search", "code_context", "code_ask", "code_index", "web_search", "web_read", "research", "vision"] {
assert!(
defs.iter().any(|d| d["name"] == t && d["inputSchema"].is_object()),
"{t} missing a definition"
);
}
}
#[tokio::test]
async fn test_proc_execute() {
let registry = ToolRegistry::new();
let result = registry.execute("exec", json!({
"action": "help"
})).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_fs_execute() {
let registry = ToolRegistry::new();
let result = registry.execute("fs", json!({
"action": "help"
})).await;
assert!(result.is_ok());
}
#[test]
fn test_version() {
let v = version();
assert!(v.get("name").is_some());
assert!(v.get("version").is_some());
assert!(v.get("tools").is_some());
}
}