1use futures::stream::SplitSink;
2use futures::{SinkExt, StreamExt};
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::Duration;
6use tokio::sync::{mpsc, oneshot, Mutex};
7use tokio::time::timeout;
8use tracing::{debug, error, info, warn};
9
10use crate::{
11 error::{MCPError, Result},
12 retry::RetryConfig,
13 schema::*,
14 transport::{Transport, TransportStream},
15};
16
17enum ResponseOrError {
19 Response(JSONRPCResponse),
20 Error(JSONRPCError),
21}
22
23#[derive(Clone, Debug)]
25pub struct ClientConfig {
26 pub retry: RetryConfig,
28 pub request_timeout: Duration,
30}
31
32impl Default for ClientConfig {
33 fn default() -> Self {
34 Self {
35 retry: RetryConfig::default(),
36 request_timeout: Duration::from_secs(30),
37 }
38 }
39}
40
41pub struct MCPClient {
43 transport_tx: Option<SplitSink<Box<dyn TransportStream>, JSONRPCMessage>>,
44 pending_requests: Arc<Mutex<HashMap<String, oneshot::Sender<ResponseOrError>>>>,
45 notification_tx: mpsc::Sender<JSONRPCNotification>,
46 notification_rx: Option<mpsc::Receiver<JSONRPCNotification>>,
47 next_request_id: Arc<Mutex<u64>>,
48 config: ClientConfig,
49}
50
51impl MCPClient {
52 pub fn new() -> Self {
54 Self::with_config(ClientConfig::default())
55 }
56
57 pub fn with_config(config: ClientConfig) -> Self {
59 let (notification_tx, notification_rx) = mpsc::channel(100);
60
61 Self {
62 transport_tx: None,
63 pending_requests: Arc::new(Mutex::new(HashMap::new())),
64 notification_tx,
65 notification_rx: Some(notification_rx),
66 next_request_id: Arc::new(Mutex::new(1)),
67 config,
68 }
69 }
70
71 pub async fn connect(&mut self, mut transport: Box<dyn Transport>) -> Result<()> {
73 transport.connect().await?;
74 let stream = transport.framed()?;
75
76 self.start_message_handler(stream).await?;
78
79 info!("MCP client connected");
80 Ok(())
81 }
82
83 pub async fn initialize(
85 &mut self,
86 client_info: Implementation,
87 capabilities: ClientCapabilities,
88 ) -> Result<InitializeResult> {
89 let request = ClientRequest::Initialize {
90 protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
91 capabilities,
92 client_info,
93 };
94
95 let value = self.request(request).await?;
96 let result: InitializeResult = serde_json::from_value(value)?;
97
98 self.send_notification("notifications/initialized", None)
100 .await?;
101
102 Ok(result)
103 }
104
105 pub async fn list_tools(&mut self) -> Result<ListToolsResult> {
107 let value = self.request(ClientRequest::ListTools).await?;
108 let result: ListToolsResult = serde_json::from_value(value)?;
109 Ok(result)
110 }
111
112 pub async fn call_tool(
114 &mut self,
115 name: String,
116 arguments: Option<serde_json::Value>,
117 ) -> Result<CallToolResult> {
118 let arguments = arguments.map(|args| {
119 if let serde_json::Value::Object(map) = args {
120 map.into_iter().collect()
121 } else {
122 std::collections::HashMap::new()
123 }
124 });
125
126 let request = ClientRequest::CallTool { name, arguments };
127 let value = self.request_with_retry(request).await?;
128 let result: CallToolResult = serde_json::from_value(value)?;
129 Ok(result)
130 }
131
132 pub fn take_notification_receiver(&mut self) -> Option<mpsc::Receiver<JSONRPCNotification>> {
134 self.notification_rx.take()
135 }
136
137 async fn request_with_retry(&mut self, request: ClientRequest) -> Result<serde_json::Value> {
139 self.request(request).await
142 }
143
144 async fn request(&mut self, request: ClientRequest) -> Result<serde_json::Value> {
146 let id = self.next_request_id().await;
147 let (tx, rx) = oneshot::channel();
148
149 {
151 let mut pending = self.pending_requests.lock().await;
152 pending.insert(id.clone(), tx);
153 }
154
155 let jsonrpc_request = JSONRPCRequest {
157 jsonrpc: JSONRPC_VERSION.to_string(),
158 id: RequestId::String(id.clone()),
159 request: Request {
160 method: request.method().to_string(),
161 params: Some(RequestParams {
162 meta: None,
163 other: serde_json::to_value(&request)?
164 .as_object()
165 .unwrap_or(&serde_json::Map::new())
166 .iter()
167 .map(|(k, v)| (k.clone(), v.clone()))
168 .collect(),
169 }),
170 },
171 };
172
173 self.send_message(JSONRPCMessage::Request(jsonrpc_request))
174 .await?;
175
176 match timeout(self.config.request_timeout, rx).await {
178 Ok(Ok(response_or_error)) => {
179 match response_or_error {
180 ResponseOrError::Response(response) => {
181 Ok(serde_json::to_value(response.result)?)
184 }
185 ResponseOrError::Error(error) => {
186 match error.error.code {
188 METHOD_NOT_FOUND => Err(MCPError::MethodNotFound(error.error.message)),
189 INVALID_PARAMS => Err(MCPError::invalid_params(
190 request.method(),
191 error.error.message,
192 )),
193 _ => Err(MCPError::Protocol(format!(
194 "JSON-RPC error {}: {}",
195 error.error.code, error.error.message
196 ))),
197 }
198 }
199 }
200 }
201 Ok(Err(e)) => {
202 error!("Response channel closed for request {}: {}", id, e);
203 self.pending_requests.lock().await.remove(&id);
205 Err(MCPError::Protocol("Response channel closed".to_string()))
206 }
207 Err(_) => {
208 error!(
210 "Request {} timed out after {:?}",
211 id, self.config.request_timeout
212 );
213 self.pending_requests.lock().await.remove(&id);
215 Err(MCPError::timeout(self.config.request_timeout, id))
216 }
217 }
218 }
219
220 async fn send_message(&mut self, message: JSONRPCMessage) -> Result<()> {
222 if let Some(transport_tx) = &mut self.transport_tx {
223 transport_tx.send(message).await?;
224 Ok(())
225 } else {
226 Err(MCPError::Transport("Not connected".to_string()))
227 }
228 }
229
230 async fn send_notification(
232 &mut self,
233 method: &str,
234 params: Option<serde_json::Value>,
235 ) -> Result<()> {
236 let notification_params = params.map(|v| NotificationParams {
237 meta: None,
238 other: if let Some(obj) = v.as_object() {
239 obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
240 } else {
241 HashMap::new()
242 },
243 });
244
245 let notification = JSONRPCNotification {
246 jsonrpc: JSONRPC_VERSION.to_string(),
247 notification: Notification {
248 method: method.to_string(),
249 params: notification_params,
250 },
251 };
252
253 self.send_message(JSONRPCMessage::Notification(notification))
254 .await
255 }
256
257 async fn next_request_id(&self) -> String {
259 let mut id = self.next_request_id.lock().await;
260 let current = *id;
261 *id += 1;
262 format!("req-{current}")
263 }
264
265 async fn start_message_handler(&mut self, stream: Box<dyn TransportStream>) -> Result<()> {
267 let pending_requests = self.pending_requests.clone();
268 let notification_tx = self.notification_tx.clone();
269
270 let (tx, mut rx) = stream.split();
272
273 self.transport_tx = Some(tx);
275
276 tokio::spawn(async move {
278 debug!("Message handler started");
279
280 while let Some(result) = rx.next().await {
281 match result {
282 Ok(message) => {
283 debug!("Received message: {:?}", message);
284
285 match message {
286 JSONRPCMessage::Response(response) => {
287 if let RequestId::String(id) = &response.id {
289 let mut pending = pending_requests.lock().await;
290 if let Some(tx) = pending.remove(id) {
291 let _ = tx.send(ResponseOrError::Response(response));
293 } else {
294 warn!("Received response for unknown request ID: {}", id);
295 }
296 }
297 }
298 JSONRPCMessage::Notification(notification) => {
299 if let Err(e) = notification_tx.send(notification).await {
301 error!("Failed to send notification: {}", e);
302 break;
304 }
305 }
306 JSONRPCMessage::Error(error) => {
307 if let RequestId::String(id) = &error.id {
309 let mut pending = pending_requests.lock().await;
310 if let Some(tx) = pending.remove(id) {
311 let _ = tx.send(ResponseOrError::Error(error));
312 } else {
313 warn!("Received error for unknown request ID: {}", id);
314 }
315 } else {
316 error!(
317 "Received error with non-string request ID: {:?}",
318 error.id
319 );
320 }
321 }
322 JSONRPCMessage::Request(_request) => {
323 warn!("Received unexpected request from server");
325 }
326 JSONRPCMessage::BatchRequest(_batch) => {
327 warn!("Received unexpected batch request from server");
329 }
330 JSONRPCMessage::BatchResponse(_batch) => {
331 warn!(
333 "Received batch response - batch requests not yet implemented"
334 );
335 }
336 }
337 }
338 Err(e) => {
339 error!("Error receiving message: {}", e);
340 break;
342 }
343 }
344 }
345
346 info!("Message handler stopped");
347 });
348
349 Ok(())
350 }
351}
352
353impl Default for MCPClient {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_client_creation() {
365 let client = MCPClient::new();
366 assert!(client.transport_tx.is_none());
367 }
368
369 #[tokio::test]
370 async fn test_next_request_id() {
371 let client = MCPClient::new();
372 let id1 = client.next_request_id().await;
373 let id2 = client.next_request_id().await;
374
375 assert_eq!(id1, "req-1");
376 assert_eq!(id2, "req-2");
377 }
378}