mcp_tools/common/
client_base.rs1use super::*;
4use crate::{McpToolsError, Result};
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tracing::{debug, error, info};
8
9#[async_trait::async_trait]
11pub trait McpClientBase: Send + Sync {
12 async fn connect(&mut self) -> Result<()>;
14
15 async fn disconnect(&mut self) -> Result<()>;
17
18 async fn get_server_capabilities(&self) -> Result<ServerCapabilities>;
20
21 async fn execute_tool(&self, request: McpToolRequest) -> Result<McpToolResponse>;
23
24 async fn get_status(&self) -> Result<ConnectionStatus>;
26}
27
28pub struct BaseClient {
30 config: ClientConfig,
32 status: Arc<RwLock<ConnectionStatus>>,
34 capabilities: Arc<RwLock<Option<ServerCapabilities>>>,
36}
37
38impl BaseClient {
39 pub fn new(config: ClientConfig) -> Self {
41 Self {
42 config,
43 status: Arc::new(RwLock::new(ConnectionStatus::Disconnected)),
44 capabilities: Arc::new(RwLock::new(None)),
45 }
46 }
47
48 pub fn config(&self) -> &ClientConfig {
50 &self.config
51 }
52
53 async fn set_status(&self, status: ConnectionStatus) {
55 let mut current_status = self.status.write().await;
56 *current_status = status;
57 }
58
59 async fn cache_capabilities(&self, capabilities: ServerCapabilities) {
61 let mut cached = self.capabilities.write().await;
62 *cached = Some(capabilities);
63 }
64}
65
66#[async_trait::async_trait]
67impl McpClientBase for BaseClient {
68 async fn connect(&mut self) -> Result<()> {
69 info!("Connecting to MCP server: {}", self.config.server_url);
70
71 self.set_status(ConnectionStatus::Connecting).await;
72
73 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
76
77 self.set_status(ConnectionStatus::Connected).await;
78 info!("Connected to MCP server successfully");
79
80 Ok(())
81 }
82
83 async fn disconnect(&mut self) -> Result<()> {
84 info!("Disconnecting from MCP server");
85
86 self.set_status(ConnectionStatus::Disconnected).await;
89
90 let mut cached = self.capabilities.write().await;
92 *cached = None;
93
94 info!("Disconnected from MCP server");
95 Ok(())
96 }
97
98 async fn get_server_capabilities(&self) -> Result<ServerCapabilities> {
99 {
101 let cached = self.capabilities.read().await;
102 if let Some(capabilities) = cached.as_ref() {
103 return Ok(capabilities.clone());
104 }
105 }
106
107 let capabilities = ServerCapabilities {
110 tools: vec![],
111 features: vec!["mock".to_string()],
112 info: ServerInfo {
113 name: "Mock Server".to_string(),
114 version: "1.0.0".to_string(),
115 description: "Mock MCP Server".to_string(),
116 coderlib_version: "0.1.0".to_string(), protocol_version: "1.0".to_string(),
118 },
119 };
120
121 self.cache_capabilities(capabilities.clone()).await;
122 Ok(capabilities)
123 }
124
125 async fn execute_tool(&self, request: McpToolRequest) -> Result<McpToolResponse> {
126 debug!("Executing tool: {}", request.tool);
127
128 let status = self.status.read().await;
130 if *status != ConnectionStatus::Connected {
131 return Err(McpToolsError::Client("Not connected to server".to_string()));
132 }
133
134 Ok(McpToolResponse::error(
137 request.id,
138 "Tool execution not implemented in base client",
139 ))
140 }
141
142 async fn get_status(&self) -> Result<ConnectionStatus> {
143 let status = self.status.read().await;
144 Ok(status.clone())
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[tokio::test]
153 async fn test_base_client_creation() {
154 let config = ClientConfig::default();
155 let client = BaseClient::new(config);
156
157 let status = client.get_status().await.unwrap();
158 assert_eq!(status, ConnectionStatus::Disconnected);
159 }
160
161 #[tokio::test]
162 async fn test_client_connection() {
163 let config = ClientConfig::default();
164 let mut client = BaseClient::new(config);
165
166 client.connect().await.unwrap();
167
168 let status = client.get_status().await.unwrap();
169 assert_eq!(status, ConnectionStatus::Connected);
170
171 client.disconnect().await.unwrap();
172
173 let status = client.get_status().await.unwrap();
174 assert_eq!(status, ConnectionStatus::Disconnected);
175 }
176}