Skip to main content

ares/mcp/
client.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Deserialize)]
5pub struct McpServerConfig {
6    pub name: String,
7    pub enabled: bool,
8    pub command: Option<String>,
9    pub args: Option<Vec<String>>,
10    pub timeout_secs: Option<u64>,
11    pub endpoint: Option<String>,
12    pub transport: Option<String>,
13    pub api_key: Option<String>,
14}
15
16pub struct McpClient {
17    config: McpServerConfig,
18    http: reqwest::Client,
19}
20
21#[derive(Debug, thiserror::Error)]
22pub enum McpError {
23    #[error("HTTP request failed: {0}")]
24    Http(#[from] reqwest::Error),
25    #[error("MCP server returned error: {0}")]
26    ServerError(String),
27    #[error("Tool not found: {0}")]
28    ToolNotFound(String),
29    #[error("Deserialize error: {0}")]
30    Deserialize(#[from] serde_json::Error),
31    #[error("MCP server is disabled")]
32    ServerDisabled,
33    #[error("No endpoint configured")]
34    NoEndpoint,
35}
36
37impl McpClient {
38    pub fn new(config: McpServerConfig) -> Self {
39        let http = reqwest::Client::builder()
40            .timeout(std::time::Duration::from_secs(
41                config.timeout_secs.unwrap_or(30),
42            ))
43            .build()
44            .expect("Failed to create HTTP client");
45
46        Self { config, http }
47    }
48
49    pub fn is_enabled(&self) -> bool {
50        self.config.enabled
51    }
52
53    pub fn name(&self) -> &str {
54        &self.config.name
55    }
56
57    pub async fn get_context(&self, path: &str) -> Result<Value, McpError> {
58        let base_url = self.get_base_url()?;
59        let url = format!("{}/api/v1/context?path={}", base_url, path);
60        
61        let mut request = self.http.get(&url);
62        if let Some(ref key) = self.config.api_key {
63            request = request.header("Authorization", format!("Bearer {}", key));
64        }
65        
66        let response = request.send().await?;
67        self.handle_response(response).await
68    }
69
70    pub async fn write_context(&self, path: &str, value: &str) -> Result<Value, McpError> {
71        let base_url = self.get_base_url()?;
72        let url = format!("{}/api/v1/context", base_url);
73        
74        let body = serde_json::json!({
75            "path": path,
76            "value": value
77        });
78        
79        let mut request = self.http.post(&url).json(&body);
80        if let Some(ref key) = self.config.api_key {
81            request = request.header("Authorization", format!("Bearer {}", key));
82        }
83        
84        let response = request.send().await?;
85        self.handle_response(response).await
86    }
87
88    pub async fn search_context(&self, query: &str, scope: Option<&str>, max_results: Option<usize>) -> Result<Value, McpError> {
89        let base_url = self.get_base_url()?;
90        let url = format!("{}/api/v1/context/search", base_url);
91        
92        let mut body = serde_json::json!({
93            "query": query
94        });
95        if let Some(s) = scope {
96            body["scope"] = serde_json::json!(s);
97        }
98        if let Some(m) = max_results {
99            body["max_results"] = serde_json::json!(m);
100        }
101        
102        let mut request = self.http.post(&url).json(&body);
103        if let Some(ref key) = self.config.api_key {
104            request = request.header("Authorization", format!("Bearer {}", key));
105        }
106        
107        let response = request.send().await?;
108        self.handle_response(response).await
109    }
110
111    pub async fn get_completeness(&self, scope: Option<&str>) -> Result<Value, McpError> {
112        let base_url = self.get_base_url()?;
113        let scope_part = scope.unwrap_or("*");
114        let url = format!("{}/api/v1/completeness/{}", base_url, scope_part);
115        
116        let mut request = self.http.get(&url);
117        if let Some(ref key) = self.config.api_key {
118            request = request.header("Authorization", format!("Bearer {}", key));
119        }
120        
121        let response = request.send().await?;
122        self.handle_response(response).await
123    }
124
125    pub async fn get_gaps(&self, status: Option<&str>, category: Option<&str>) -> Result<Value, McpError> {
126        let base_url = self.get_base_url()?;
127        let url = format!("{}/api/v1/gaps", base_url);
128        
129        let mut request = self.http.get(&url);
130        if let Some(ref key) = self.config.api_key {
131            request = request.header("Authorization", format!("Bearer {}", key));
132        }
133        
134        let response = request.send().await?;
135        self.handle_response(response).await
136    }
137
138    pub async fn detect_gaps(&self, category: Option<&str>) -> Result<Value, McpError> {
139        let base_url = self.get_base_url()?;
140        let url = format!("{}/api/v1/gaps/detect", base_url);
141        
142        let body = if let Some(cat) = category {
143            serde_json::json!({ "category": cat })
144        } else {
145            serde_json::json!({})
146        };
147        
148        let mut request = self.http.post(&url).json(&body);
149        if let Some(ref key) = self.config.api_key {
150            request = request.header("Authorization", format!("Bearer {}", key));
151        }
152        
153        let response = request.send().await?;
154        self.handle_response(response).await
155    }
156
157    fn get_base_url(&self) -> Result<String, McpError> {
158        self.config
159            .endpoint
160            .clone()
161            .ok_or(McpError::NoEndpoint)
162    }
163
164    async fn handle_response(&self, response: reqwest::Response) -> Result<Value, McpError> {
165        if !response.status().is_success() {
166            let status = response.status();
167            let text = response.text().await.unwrap_or_default();
168            return Err(McpError::ServerError(format!("HTTP {}: {}", status, text)));
169        }
170
171        let result: Value = response.json().await?;
172        Ok(result)
173    }
174}