1use serde_json::Value;
4use std::sync::Arc;
5use tokio::sync::mpsc;
6use tracing::{debug, info, warn};
7
8use crate::error::{McpClientError, McpClientResult};
9use crate::transport::ServerEvent;
10
11#[derive(Debug)]
13pub struct StreamHandler {
14 event_receiver: Option<mpsc::UnboundedReceiver<ServerEvent>>,
16 callbacks: Arc<parking_lot::Mutex<StreamCallbacks>>,
18 response_sender: Option<mpsc::UnboundedSender<Value>>,
20}
21
22type RequestHandler = Box<dyn Fn(Value) -> Result<Value, String> + Send + Sync>;
24
25#[derive(Default)]
27pub struct StreamCallbacks {
28 pub notification: Option<Box<dyn Fn(Value) + Send + Sync>>,
30 pub request: Option<RequestHandler>,
32 pub connection_lost: Option<Box<dyn Fn() + Send + Sync>>,
34 pub error: Option<Box<dyn Fn(String) + Send + Sync>>,
36 pub heartbeat: Option<Box<dyn Fn() + Send + Sync>>,
38}
39
40impl std::fmt::Debug for StreamCallbacks {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("StreamCallbacks")
43 .field(
44 "notification",
45 &self.notification.as_ref().map(|_| "function"),
46 )
47 .field("request", &self.request.as_ref().map(|_| "function"))
48 .field(
49 "connection_lost",
50 &self.connection_lost.as_ref().map(|_| "function"),
51 )
52 .field("error", &self.error.as_ref().map(|_| "function"))
53 .field("heartbeat", &self.heartbeat.as_ref().map(|_| "function"))
54 .finish()
55 }
56}
57
58impl StreamHandler {
59 pub fn new() -> Self {
61 Self {
62 event_receiver: None,
63 callbacks: Arc::new(parking_lot::Mutex::new(StreamCallbacks::default())),
64 response_sender: None,
65 }
66 }
67
68 pub fn set_receiver(&mut self, receiver: mpsc::UnboundedReceiver<ServerEvent>) {
70 self.event_receiver = Some(receiver);
71 }
72
73 pub fn set_response_sender(&mut self, sender: mpsc::UnboundedSender<Value>) {
75 self.response_sender = Some(sender);
76 }
77
78 pub fn on_notification<F>(&self, callback: F)
80 where
81 F: Fn(Value) + Send + Sync + 'static,
82 {
83 self.callbacks.lock().notification = Some(Box::new(callback));
84 }
85
86 pub fn on_request<F>(&self, callback: F)
88 where
89 F: Fn(Value) -> Result<Value, String> + Send + Sync + 'static,
90 {
91 self.callbacks.lock().request = Some(Box::new(callback));
92 }
93
94 pub fn on_connection_lost<F>(&self, callback: F)
96 where
97 F: Fn() + Send + Sync + 'static,
98 {
99 self.callbacks.lock().connection_lost = Some(Box::new(callback));
100 }
101
102 pub fn on_error<F>(&self, callback: F)
104 where
105 F: Fn(String) + Send + Sync + 'static,
106 {
107 self.callbacks.lock().error = Some(Box::new(callback));
108 }
109
110 pub fn on_heartbeat<F>(&self, callback: F)
112 where
113 F: Fn() + Send + Sync + 'static,
114 {
115 self.callbacks.lock().heartbeat = Some(Box::new(callback));
116 }
117
118 pub async fn start(&mut self) -> McpClientResult<()> {
120 let mut receiver = self
121 .event_receiver
122 .take()
123 .ok_or_else(|| McpClientError::generic("No event receiver configured"))?;
124
125 let callbacks = Arc::clone(&self.callbacks);
126 let response_sender = self.response_sender.clone();
127
128 tokio::spawn(async move {
129 info!("Stream handler started");
130
131 while let Some(event) = receiver.recv().await {
132 debug!(event = ?event, "Received server event");
133
134 let callbacks = callbacks.lock();
135
136 match event {
137 ServerEvent::Notification(notification) => {
138 if let Some(ref callback) = callbacks.notification {
139 callback(notification);
140 }
141 }
142 ServerEvent::Request(request) => {
143 let request_id = request.get("id").filter(|id| !id.is_null()).cloned();
147
148 if request_id.is_none() {
149 warn!(
150 "Received server request without valid id, treating as notification"
151 );
152 if let Some(ref callback) = callbacks.request {
153 let _ = callback(request);
154 }
155 continue;
156 }
157
158 if let Some(ref callback) = callbacks.request {
159 let response_json = match callback(request) {
160 Ok(result) => {
161 debug!("Request handled successfully");
162 serde_json::json!({
163 "jsonrpc": "2.0",
164 "id": request_id,
165 "result": result
166 })
167 }
168 Err(error) => {
169 warn!(error = %error, "Request handler returned error");
170 serde_json::json!({
171 "jsonrpc": "2.0",
172 "id": request_id,
173 "error": {
174 "code": -32603,
175 "message": error
176 }
177 })
178 }
179 };
180
181 if let Some(ref sender) = response_sender {
182 if let Err(e) = sender.send(response_json) {
183 warn!("Failed to send response via channel: {}", e);
184 }
185 } else {
186 warn!("No response sender configured, response discarded");
187 }
188 } else {
189 warn!("Received server request but no request handler configured");
190 if let Some(ref sender) = response_sender {
191 let error_json = serde_json::json!({
192 "jsonrpc": "2.0",
193 "id": request_id,
194 "error": {
195 "code": -32601,
196 "message": "Method not found: no request handler configured"
197 }
198 });
199 if let Err(e) = sender.send(error_json) {
200 warn!("Failed to send error response via channel: {}", e);
201 }
202 }
203 }
204 }
205 ServerEvent::Response(_) => {
206 debug!("Received async response via event stream");
210 }
211 ServerEvent::ConnectionLost => {
212 warn!("Connection lost");
213 if let Some(ref callback) = callbacks.connection_lost {
214 callback();
215 }
216 }
217 ServerEvent::Error(error) => {
218 warn!(error = %error, "Server error");
219 if let Some(ref callback) = callbacks.error {
220 callback(error);
221 }
222 }
223 ServerEvent::Heartbeat => {
224 debug!("Heartbeat received");
225 if let Some(ref callback) = callbacks.heartbeat {
226 callback();
227 }
228 }
229 }
230 }
231
232 info!("Stream handler stopped");
233 });
234
235 Ok(())
236 }
237
238 pub fn is_active(&self) -> bool {
240 self.event_receiver.is_some()
241 }
242}
243
244impl Default for StreamHandler {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct ProgressTracker {
253 pub operation_id: String,
255 pub total: Option<u64>,
257 pub completed: u64,
259 pub message: Option<String>,
261 pub metadata: Value,
263}
264
265impl ProgressTracker {
266 pub fn new(operation_id: String) -> Self {
268 Self {
269 operation_id,
270 total: None,
271 completed: 0,
272 message: None,
273 metadata: Value::Null,
274 }
275 }
276
277 pub fn update(&mut self, completed: u64, message: Option<String>) {
279 self.completed = completed;
280 self.message = message;
281 }
282
283 pub fn set_total(&mut self, total: u64) {
285 self.total = Some(total);
286 }
287
288 pub fn percentage(&self) -> Option<f64> {
290 self.total.map(|total| {
291 if total == 0 {
292 1.0
293 } else {
294 (self.completed as f64) / (total as f64)
295 }
296 })
297 }
298
299 pub fn is_complete(&self) -> bool {
301 if let Some(total) = self.total {
302 self.completed >= total
303 } else {
304 false
305 }
306 }
307
308 pub fn status(&self) -> String {
310 match (self.total, &self.message) {
311 (Some(total), Some(msg)) => {
312 format!(
313 "{}/{} ({}%) - {}",
314 self.completed,
315 total,
316 (self.percentage().unwrap_or(0.0) * 100.0) as u32,
317 msg
318 )
319 }
320 (Some(total), None) => {
321 format!(
322 "{}/{} ({}%)",
323 self.completed,
324 total,
325 (self.percentage().unwrap_or(0.0) * 100.0) as u32
326 )
327 }
328 (None, Some(msg)) => {
329 format!("{} steps - {}", self.completed, msg)
330 }
331 (None, None) => {
332 format!("{} steps", self.completed)
333 }
334 }
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_progress_tracker() {
344 let mut tracker = ProgressTracker::new("test-op".to_string());
345
346 assert_eq!(tracker.completed, 0);
347 assert_eq!(tracker.percentage(), None);
348
349 tracker.set_total(100);
350 assert_eq!(tracker.percentage(), Some(0.0));
351
352 tracker.update(50, Some("halfway".to_string()));
353 assert_eq!(tracker.percentage(), Some(0.5));
354 assert_eq!(tracker.message, Some("halfway".to_string()));
355
356 tracker.update(100, Some("complete".to_string()));
357 assert_eq!(tracker.percentage(), Some(1.0));
358 assert!(tracker.is_complete());
359 }
360
361 #[tokio::test]
362 async fn test_stream_handler_callbacks() {
363 let handler = StreamHandler::new();
364
365 let notification_received = Arc::new(parking_lot::Mutex::new(false));
366 let notification_received_clone = Arc::clone(¬ification_received);
367
368 handler.on_notification(move |_| {
369 *notification_received_clone.lock() = true;
370 });
371
372 assert!(handler.callbacks.lock().notification.is_some());
374 }
375
376 #[tokio::test]
377 async fn test_stream_handler_sends_success_response() {
378 let (event_tx, event_rx) = mpsc::unbounded_channel();
379 let (response_tx, mut response_rx) = mpsc::unbounded_channel();
380
381 let mut handler = StreamHandler::new();
382 handler.set_receiver(event_rx);
383 handler.set_response_sender(response_tx);
384
385 handler.on_request(|_req| Ok(serde_json::json!({"status": "ok"})));
386
387 handler.start().await.unwrap();
388
389 let request = serde_json::json!({
391 "jsonrpc": "2.0",
392 "id": "srv-1",
393 "method": "sampling/createMessage",
394 "params": {}
395 });
396 event_tx.send(ServerEvent::Request(request)).unwrap();
397
398 let response = tokio::time::timeout(std::time::Duration::from_secs(1), response_rx.recv())
400 .await
401 .unwrap()
402 .unwrap();
403
404 assert_eq!(response["jsonrpc"], "2.0");
405 assert_eq!(response["id"], "srv-1");
406 assert_eq!(response["result"]["status"], "ok");
407 assert!(response.get("error").is_none());
408 }
409
410 #[tokio::test]
411 async fn test_stream_handler_sends_error_response() {
412 let (event_tx, event_rx) = mpsc::unbounded_channel();
413 let (response_tx, mut response_rx) = mpsc::unbounded_channel();
414
415 let mut handler = StreamHandler::new();
416 handler.set_receiver(event_rx);
417 handler.set_response_sender(response_tx);
418
419 handler.on_request(|_req| Err("something went wrong".to_string()));
420
421 handler.start().await.unwrap();
422
423 let request = serde_json::json!({
424 "jsonrpc": "2.0",
425 "id": 42,
426 "method": "sampling/createMessage",
427 "params": {}
428 });
429 event_tx.send(ServerEvent::Request(request)).unwrap();
430
431 let response = tokio::time::timeout(std::time::Duration::from_secs(1), response_rx.recv())
432 .await
433 .unwrap()
434 .unwrap();
435
436 assert_eq!(response["jsonrpc"], "2.0");
437 assert_eq!(response["id"], 42);
438 assert_eq!(response["error"]["code"], -32603);
439 assert_eq!(response["error"]["message"], "something went wrong");
440 }
441
442 #[tokio::test]
443 async fn test_stream_handler_no_callback_sends_method_not_found() {
444 let (event_tx, event_rx) = mpsc::unbounded_channel();
445 let (response_tx, mut response_rx) = mpsc::unbounded_channel();
446
447 let mut handler = StreamHandler::new();
448 handler.set_receiver(event_rx);
449 handler.set_response_sender(response_tx);
450 handler.start().await.unwrap();
453
454 let request = serde_json::json!({
455 "jsonrpc": "2.0",
456 "id": "req-99",
457 "method": "unknown/method",
458 "params": {}
459 });
460 event_tx.send(ServerEvent::Request(request)).unwrap();
461
462 let response = tokio::time::timeout(std::time::Duration::from_secs(1), response_rx.recv())
463 .await
464 .unwrap()
465 .unwrap();
466
467 assert_eq!(response["jsonrpc"], "2.0");
468 assert_eq!(response["id"], "req-99");
469 assert_eq!(response["error"]["code"], -32601);
470 }
471
472 #[tokio::test]
473 async fn test_stream_handler_no_id_skips_response() {
474 let (event_tx, event_rx) = mpsc::unbounded_channel();
475 let (response_tx, mut response_rx) = mpsc::unbounded_channel();
476
477 let mut handler = StreamHandler::new();
478 handler.set_receiver(event_rx);
479 handler.set_response_sender(response_tx);
480
481 let callback_called = Arc::new(parking_lot::Mutex::new(false));
482 let callback_called_clone = Arc::clone(&callback_called);
483 handler.on_request(move |_req| {
484 *callback_called_clone.lock() = true;
485 Ok(serde_json::json!({"handled": true}))
486 });
487
488 handler.start().await.unwrap();
489
490 let request = serde_json::json!({
492 "jsonrpc": "2.0",
493 "method": "sampling/createMessage",
494 "params": {}
495 });
496 event_tx.send(ServerEvent::Request(request)).unwrap();
497
498 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
500
501 assert!(
503 *callback_called.lock(),
504 "callback should be invoked even without id"
505 );
506
507 let result =
509 tokio::time::timeout(std::time::Duration::from_millis(100), response_rx.recv()).await;
510 assert!(
511 result.is_err(),
512 "no response should be sent for request without id"
513 );
514 }
515
516 #[tokio::test]
517 async fn test_stream_handler_null_id_skips_response() {
518 let (event_tx, event_rx) = mpsc::unbounded_channel();
519 let (response_tx, mut response_rx) = mpsc::unbounded_channel();
520
521 let mut handler = StreamHandler::new();
522 handler.set_receiver(event_rx);
523 handler.set_response_sender(response_tx);
524
525 handler.on_request(|_req| Ok(serde_json::json!({"handled": true})));
526
527 handler.start().await.unwrap();
528
529 let request = serde_json::json!({
531 "jsonrpc": "2.0",
532 "id": null,
533 "method": "sampling/createMessage",
534 "params": {}
535 });
536 event_tx.send(ServerEvent::Request(request)).unwrap();
537
538 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
539
540 let result =
541 tokio::time::timeout(std::time::Duration::from_millis(100), response_rx.recv()).await;
542 assert!(
543 result.is_err(),
544 "no response should be sent for request with null id"
545 );
546 }
547}