mcp-tools 0.1.0

Rust MCP tools library
Documentation
//! Desktop MCP Client
//!
//! Desktop application interface for MCP servers
//! Provides a GUI for interactive tool execution and server management

use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug, error, info, warn};

use crate::common::{
    BaseClient, ClientConfig, ConnectionStatus, McpClientBase, McpToolRequest, McpToolResponse,
    ServerCapabilities,
};
use crate::{McpToolsError, Result};

/// Desktop application configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopConfig {
    pub window_title: String,
    pub window_width: u32,
    pub window_height: u32,
    pub theme: String,
    pub auto_connect: bool,
    pub save_history: bool,
    pub max_history_items: usize,
}

impl Default for DesktopConfig {
    fn default() -> Self {
        Self {
            window_title: "MCP Tools Desktop Client".to_string(),
            window_width: 1200,
            window_height: 800,
            theme: "dark".to_string(),
            auto_connect: true,
            save_history: true,
            max_history_items: 100,
        }
    }
}

/// Tool execution history item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryItem {
    pub id: String,
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub tool_name: String,
    pub arguments: HashMap<String, serde_json::Value>,
    pub response: Option<McpToolResponse>,
    pub execution_time: u64,
    pub success: bool,
}

/// Desktop application state
#[derive(Debug)]
pub struct DesktopState {
    pub connected: bool,
    pub capabilities: Option<ServerCapabilities>,
    pub history: Vec<HistoryItem>,
    pub current_tool: Option<String>,
    pub tool_arguments: HashMap<String, serde_json::Value>,
    pub last_response: Option<McpToolResponse>,
}

impl Default for DesktopState {
    fn default() -> Self {
        Self {
            connected: false,
            capabilities: None,
            history: Vec::new(),
            current_tool: None,
            tool_arguments: HashMap::new(),
            last_response: None,
        }
    }
}

/// Desktop MCP Client
pub struct DesktopClient {
    base: BaseClient,
    config: DesktopConfig,
    state: Arc<Mutex<DesktopState>>,
}

impl DesktopClient {
    pub fn new(client_config: ClientConfig, desktop_config: DesktopConfig) -> Self {
        let base = BaseClient::new(client_config);
        let state = Arc::new(Mutex::new(DesktopState::default()));
        Self {
            base,
            config: desktop_config,
            state,
        }
    }

    /// Run the desktop application
    pub async fn run(&mut self) -> Result<()> {
        info!("Starting MCP Desktop Client");

        // Initialize the application state
        {
            let mut state = self.state.lock().await;
            state.connected = false;
        }

        // Auto-connect if configured
        if self.config.auto_connect {
            match self.connect().await {
                Ok(_) => {
                    info!("Auto-connected to MCP server");
                    let capabilities = self.get_server_capabilities().await?;
                    let mut state = self.state.lock().await;
                    state.connected = true;
                    state.capabilities = Some(capabilities);
                }
                Err(e) => {
                    warn!("Auto-connect failed: {}", e);
                }
            }
        }

        // In a real implementation, this would start the GUI framework
        // For now, we'll simulate a desktop application with a simple loop
        self.simulate_desktop_app().await
    }

    /// Simulate desktop application behavior
    async fn simulate_desktop_app(&mut self) -> Result<()> {
        println!("MCP Desktop Client Started");
        println!(
            "Window: {} ({}x{})",
            self.config.window_title, self.config.window_width, self.config.window_height
        );

        let state = self.state.lock().await;
        if state.connected {
            println!("Status: Connected to MCP server");
            if let Some(capabilities) = &state.capabilities {
                println!("Available tools: {}", capabilities.tools.len());
            }
        } else {
            println!("Status: Not connected");
        }

        println!("Desktop client simulation complete");
        Ok(())
    }

    /// Execute a tool and update history
    pub async fn execute_tool_with_history(
        &mut self,
        tool_name: String,
        arguments: HashMap<String, serde_json::Value>,
    ) -> Result<McpToolResponse> {
        let start_time = std::time::Instant::now();
        let request_id = uuid::Uuid::new_v4().to_string();

        let request = McpToolRequest {
            id: uuid::Uuid::new_v4(),
            tool: tool_name.clone(),
            arguments: serde_json::to_value(arguments.clone())?,
            session_id: uuid::Uuid::new_v4().to_string(),
            metadata: HashMap::new(),
        };

        let result = self.execute_tool(request).await;
        let execution_time = start_time.elapsed().as_millis() as u64;

        // Update history
        let history_item = HistoryItem {
            id: request_id,
            timestamp: chrono::Utc::now(),
            tool_name: tool_name.clone(),
            arguments,
            response: result.as_ref().ok().cloned(),
            execution_time,
            success: result.is_ok(),
        };

        {
            let mut state = self.state.lock().await;
            state.history.push(history_item);

            // Limit history size
            if state.history.len() > self.config.max_history_items {
                state.history.remove(0);
            }

            // Update last response
            if let Ok(ref response) = result {
                state.last_response = Some(response.clone());
            }
        }

        result
    }

    /// Get execution history
    pub async fn get_history(&self) -> Vec<HistoryItem> {
        let state = self.state.lock().await;
        state.history.clone()
    }

    /// Clear execution history
    pub async fn clear_history(&mut self) -> Result<()> {
        let mut state = self.state.lock().await;
        state.history.clear();
        info!("Execution history cleared");
        Ok(())
    }

    /// Get application state
    pub async fn get_state(&self) -> DesktopState {
        let state = self.state.lock().await;
        DesktopState {
            connected: state.connected,
            capabilities: state.capabilities.clone(),
            history: state.history.clone(),
            current_tool: state.current_tool.clone(),
            tool_arguments: state.tool_arguments.clone(),
            last_response: state.last_response.clone(),
        }
    }

    /// Set current tool for the UI
    pub async fn set_current_tool(&mut self, tool_name: Option<String>) -> Result<()> {
        let mut state = self.state.lock().await;
        state.current_tool = tool_name;
        state.tool_arguments.clear();
        Ok(())
    }

    /// Set tool argument
    pub async fn set_tool_argument(&mut self, key: String, value: serde_json::Value) -> Result<()> {
        let mut state = self.state.lock().await;
        state.tool_arguments.insert(key, value);
        Ok(())
    }

    /// Execute current tool with current arguments
    pub async fn execute_current_tool(&mut self) -> Result<McpToolResponse> {
        let (tool_name, arguments) = {
            let state = self.state.lock().await;
            let tool_name = state
                .current_tool
                .clone()
                .ok_or_else(|| McpToolsError::Server("No tool selected".to_string()))?;
            let arguments = state.tool_arguments.clone();
            (tool_name, arguments)
        };

        self.execute_tool_with_history(tool_name, arguments).await
    }

    /// Get available tools
    pub async fn get_available_tools(&self) -> Result<Vec<crate::common::McpTool>> {
        let state = self.state.lock().await;
        if let Some(capabilities) = &state.capabilities {
            Ok(capabilities.tools.clone())
        } else {
            Err(McpToolsError::Server("Not connected to server".to_string()))
        }
    }

    /// Export history to JSON
    pub async fn export_history(&self) -> Result<String> {
        let state = self.state.lock().await;
        serde_json::to_string_pretty(&state.history)
            .map_err(|e| McpToolsError::Server(format!("Failed to export history: {}", e)))
    }

    /// Import history from JSON
    pub async fn import_history(&mut self, json_data: &str) -> Result<()> {
        let imported_history: Vec<HistoryItem> = serde_json::from_str(json_data)
            .map_err(|e| McpToolsError::Server(format!("Failed to import history: {}", e)))?;

        let mut state = self.state.lock().await;
        state.history.extend(imported_history);

        // Limit history size
        if state.history.len() > self.config.max_history_items {
            let excess = state.history.len() - self.config.max_history_items;
            state.history.drain(0..excess);
        }

        info!("History imported successfully");
        Ok(())
    }
}

#[async_trait]
impl McpClientBase for DesktopClient {
    async fn connect(&mut self) -> Result<()> {
        debug!("Desktop client connecting to MCP server");
        let result = self.base.connect().await;

        if result.is_ok() {
            let mut state = self.state.lock().await;
            state.connected = true;

            // Get capabilities after successful connection
            if let Ok(capabilities) = self.base.get_server_capabilities().await {
                state.capabilities = Some(capabilities);
            }
        }

        result
    }

    async fn disconnect(&mut self) -> Result<()> {
        debug!("Desktop client disconnecting from MCP server");
        let result = self.base.disconnect().await;

        if result.is_ok() {
            let mut state = self.state.lock().await;
            state.connected = false;
            state.capabilities = None;
            state.current_tool = None;
            state.tool_arguments.clear();
            state.last_response = None;
        }

        result
    }

    async fn get_server_capabilities(&self) -> Result<ServerCapabilities> {
        debug!("Desktop client getting server capabilities");
        self.base.get_server_capabilities().await
    }

    async fn execute_tool(&self, request: McpToolRequest) -> Result<McpToolResponse> {
        debug!("Desktop client executing tool: {}", request.tool);
        self.base.execute_tool(request).await
    }

    async fn get_status(&self) -> Result<ConnectionStatus> {
        debug!("Desktop client getting connection status");
        self.base.get_status().await
    }
}