aacode-rs 1.8.8

Rust port of the AACode ReAct coding agent, built on fastshell for mobile AI agents
Documentation
// Copyright (c) 2026 xiefujin <490021684@qq.com>
// Licensed under GPL-3.0, see LICENSE file for full license terms.

//! MCP tools — list_mcp_tools / call_mcp_tool / get_mcp_status.
//! All blocking pipe I/O is executed on a background thread via spawn_blocking.

use super::registry::Tool;
use super::schema::{ParamType, ToolParameter, ToolSchema};
use crate::error::Result;
use crate::mcp::McpManager;
use serde_json::Value;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

pub struct ListMcpToolsTool {
    pub mgr: Arc<McpManager>,
}
#[async_trait::async_trait]
impl Tool for ListMcpToolsTool {
    fn schema(&self) -> ToolSchema {
        ToolSchema::new(
            "list_mcp_tools",
            "List all available MCP tools from configured servers.",
            vec![],
        )
    }
    async fn call(&self, _args: &Value, _c: &AtomicBool) -> Result<String> {
        let mgr = self.mgr.clone();
        tokio::task::spawn_blocking(move || Ok(mgr.list_tools().to_string())).await.map_err(|e| crate::error::AacodeError::Other(format!("{e}")))?
    }
}

pub struct CallMcpToolTool {
    pub mgr: Arc<McpManager>,
}
#[async_trait::async_trait]
impl Tool for CallMcpToolTool {
    fn schema(&self) -> ToolSchema {
        ToolSchema::new(
            "call_mcp_tool",
            "Call an MCP tool. tool_name is 'server.tool' or just 'tool'.",
            vec![
                ToolParameter::new("tool_name", ParamType::String, true, "server.tool or tool", &["tool", "name", "function"]),
                ToolParameter::new("arguments", ParamType::Object, false, "Tool arguments", &["args", "params", "input"]),
            ],
        )
    }
    async fn call(&self, args: &Value, _c: &AtomicBool) -> Result<String> {
        let name = args.get("tool_name").and_then(|v| v.as_str()).unwrap_or("").to_string();
        let arguments = args.get("arguments").cloned().unwrap_or(serde_json::json!({}));
        let mgr = self.mgr.clone();
        tokio::task::spawn_blocking(move || Ok(mgr.call_tool(&name, arguments).to_string())).await.map_err(|e| crate::error::AacodeError::Other(format!("{e}")))?
    }
}

pub struct McpStatusTool {
    pub mgr: Arc<McpManager>,
}
#[async_trait::async_trait]
impl Tool for McpStatusTool {
    fn schema(&self) -> ToolSchema {
        ToolSchema::new("get_mcp_status", "Get MCP server status.", vec![])
    }
    async fn call(&self, _args: &Value, _c: &AtomicBool) -> Result<String> {
        let mgr = self.mgr.clone();
        tokio::task::spawn_blocking(move || Ok(mgr.status().to_string())).await.map_err(|e| crate::error::AacodeError::Other(format!("{e}")))?
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[tokio::test]
    async fn status_tool_reports_empty() {
        let mgr = Arc::new(McpManager::new(vec![], 5));
        let t = McpStatusTool { mgr };
        let cancel = AtomicBool::new(false);
        let out = t.call(&json!({}), &cancel).await.unwrap();
        let v: Value = serde_json::from_str(&out).unwrap();
        assert_eq!(v["total_count"], 0);
    }

    #[tokio::test]
    async fn list_tool_empty() {
        let mgr = Arc::new(McpManager::new(vec![], 5));
        let t = ListMcpToolsTool { mgr };
        let cancel = AtomicBool::new(false);
        let out = t.call(&json!({}), &cancel).await.unwrap();
        assert!(out.contains("\"count\":0"));
    }
}