praxis_mcp/
executor.rs

1use crate::client::{MCPClient, ToolResponse};
2use anyhow::Result;
3use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7/// Tool executor that delegates to MCP servers
8pub struct MCPToolExecutor {
9    clients: Arc<RwLock<HashMap<String, Arc<MCPClient>>>>,
10}
11
12impl MCPToolExecutor {
13    pub fn new() -> Self {
14        Self {
15            clients: Arc::new(RwLock::new(HashMap::new())),
16        }
17    }
18
19    /// Add an MCP server
20    pub async fn add_server(&self, client: MCPClient) -> Result<()> {
21        let name = client.name().to_string();
22        let mut clients = self.clients.write().await;
23        clients.insert(name, Arc::new(client));
24        Ok(())
25    }
26
27    /// List all available tools from all connected MCP servers
28    pub async fn list_all_tools(&self) -> Result<Vec<(String, Vec<crate::client::ToolInfo>)>> {
29        let clients = self.clients.read().await;
30        let mut all_tools = Vec::new();
31
32        for (server_name, client) in clients.iter() {
33            let tools = client.list_tools().await?;
34            all_tools.push((server_name.clone(), tools));
35        }
36
37        Ok(all_tools)
38    }
39
40    /// Get all tools from all connected MCP servers in LLM format
41    pub async fn get_llm_tools(&self) -> Result<Vec<praxis_llm::Tool>> {
42        let mut all_tools = Vec::new();
43        let clients = self.clients.read().await;
44        
45        for client in clients.values() {
46            let tools = client.get_llm_tools().await?;
47            all_tools.extend(tools);
48        }
49        
50        Ok(all_tools)
51    }
52
53    /// Execute a tool by finding the right MCP server
54    pub async fn execute_tool(&self, tool_name: &str, arguments: serde_json::Value) 
55        -> Result<Vec<ToolResponse>> {
56        let clients = self.clients.read().await;
57        
58        for client in clients.values() {
59            let tools = client.list_tools().await?;
60            if tools.iter().any(|t| t.name == tool_name) {
61                return client.call_tool(tool_name, arguments).await;
62            }
63        }
64        
65        Err(anyhow::anyhow!("Tool '{}' not found", tool_name))
66    }
67}
68
69// Note: We're intentionally NOT implementing the ToolExecutor trait here
70// because we want to deprecate the mock-based approach.
71// Instead, MCPToolExecutor provides its own interface that's MCP-native.
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[tokio::test]
78    async fn test_executor_creation() {
79        let executor = MCPToolExecutor::new();
80        assert!(executor.list_all_tools().await.unwrap().is_empty());
81    }
82}
83