lc/core/
tools.rs

1//! MCP tools integration for LLM function calling
2
3use anyhow::Result;
4
5// Re-export the Tool type from provider module for consistency
6pub use crate::core::provider::Tool;
7
8/// Fetch tools from specified MCP servers
9pub async fn fetch_mcp_tools(tools_str: &str) -> Result<(Option<Vec<Tool>>, Vec<String>)> {
10    use crate::services::mcp::McpConfig;
11    use crate::services::mcp_daemon::DaemonClient;
12    
13    let server_names: Vec<&str> = tools_str.split(',').map(|s| s.trim()).collect();
14    let mut all_tools = Vec::new();
15    let mut valid_server_names = Vec::new();
16
17    // Load MCP configuration
18    let config = McpConfig::load().await?;
19
20    // Use daemon client for persistent connections
21    let daemon_client = DaemonClient::new()?;
22
23    for server_name in server_names {
24        if server_name.is_empty() {
25            continue;
26        }
27
28        crate::debug_log!("Fetching tools from MCP server '{}'", server_name);
29
30        // Check if server exists in configuration
31        if config.get_server(server_name).is_some() {
32            // Ensure server is connected via daemon
33            match daemon_client.ensure_server_connected(server_name).await {
34                Ok(_) => {
35                    crate::debug_log!("Successfully connected to MCP server '{}'", server_name);
36                    valid_server_names.push(server_name.to_string());
37                }
38                Err(e) => {
39                    eprintln!(
40                        "Warning: Failed to connect to MCP server '{}': {}",
41                        server_name, e
42                    );
43                    continue;
44                }
45            }
46        } else {
47            eprintln!(
48                "Warning: MCP server '{}' not found in configuration",
49                server_name
50            );
51            continue;
52        }
53    }
54
55    // Get all tools from connected servers using daemon client
56    for server_name in &valid_server_names {
57        match daemon_client.list_tools(server_name).await {
58            Ok(server_tools) => {
59                if let Some(tools) = server_tools.get(server_name) {
60                    crate::debug_log!(
61                        "Retrieved {} tools from server '{}'",
62                        tools.len(),
63                        server_name
64                    );
65
66                    for tool in tools {
67                        // Convert MCP tool to OpenAI tool format
68                        // Simplify the schema to reduce token usage
69                        let mut simplified_schema = serde_json::Map::new();
70                        
71                        // Copy only essential fields from input_schema
72                        if let Some(properties) = tool.input_schema.get("properties") {
73                            simplified_schema.insert("type".to_string(), serde_json::json!("object"));
74                            simplified_schema.insert("properties".to_string(), properties.clone());
75                            
76                            if let Some(required) = tool.input_schema.get("required") {
77                                simplified_schema.insert("required".to_string(), required.clone());
78                            }
79                        } else {
80                            // If no properties, use minimal schema
81                            simplified_schema.insert("type".to_string(), serde_json::json!("object"));
82                            simplified_schema.insert("properties".to_string(), serde_json::json!({}));
83                        }
84                        
85                        let openai_tool = crate::core::provider::Tool {
86                            tool_type: "function".to_string(),
87                            function: crate::core::provider::Function {
88                                name: tool.name.to_string(),
89                                description: tool
90                                    .description
91                                    .as_ref()
92                                    .map(|s| s.to_string())
93                                    .unwrap_or_else(|| "No description".to_string()),
94                                parameters: serde_json::Value::Object(simplified_schema),
95                            },
96                        };
97
98                        all_tools.push(openai_tool);
99                        crate::debug_log!(
100                            "Added tool '{}' from server '{}'",
101                            tool.name,
102                            server_name
103                        );
104                    }
105                }
106            }
107            Err(e) => {
108                eprintln!(
109                    "Warning: Failed to list tools from MCP server '{}': {}",
110                    server_name, e
111                );
112            }
113        }
114    }
115
116    // Connections persist in daemon - no cleanup needed
117
118    if all_tools.is_empty() {
119        crate::debug_log!("No tools found from any specified MCP servers");
120        Ok((None, valid_server_names))
121    } else {
122        crate::debug_log!("Total {} tools fetched from MCP servers", all_tools.len());
123        Ok((Some(all_tools), valid_server_names))
124    }
125}
126
127/// Execute a tool call via MCP
128pub async fn execute_mcp_tool(
129    server_name: &str,
130    tool_name: &str,
131    arguments: serde_json::Value,
132) -> Result<serde_json::Value> {
133    use crate::services::mcp_daemon::DaemonClient;
134    
135    let daemon_client = DaemonClient::new()?;
136    
137    crate::debug_log!(
138        "Executing tool '{}' on server '{}' with arguments: {}",
139        tool_name,
140        server_name,
141        arguments
142    );
143    
144    match daemon_client.call_tool(server_name, tool_name, arguments).await {
145        Ok(result) => {
146            crate::debug_log!("Tool execution successful");
147            Ok(result)
148        }
149        Err(e) => {
150            crate::debug_log!("Tool execution failed: {}", e);
151            Err(e)
152        }
153    }
154}