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};
#[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,
}
}
}
#[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,
}
#[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,
}
}
}
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,
}
}
pub async fn run(&mut self) -> Result<()> {
info!("Starting MCP Desktop Client");
{
let mut state = self.state.lock().await;
state.connected = false;
}
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);
}
}
}
self.simulate_desktop_app().await
}
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(())
}
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;
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);
if state.history.len() > self.config.max_history_items {
state.history.remove(0);
}
if let Ok(ref response) = result {
state.last_response = Some(response.clone());
}
}
result
}
pub async fn get_history(&self) -> Vec<HistoryItem> {
let state = self.state.lock().await;
state.history.clone()
}
pub async fn clear_history(&mut self) -> Result<()> {
let mut state = self.state.lock().await;
state.history.clear();
info!("Execution history cleared");
Ok(())
}
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(),
}
}
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(())
}
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(())
}
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
}
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()))
}
}
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)))
}
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);
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;
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
}
}