turbomcp_client/handlers.rs
1//! Handler traits for bidirectional communication in MCP client
2//!
3//! This module provides handler traits and registration mechanisms for processing
4//! server-initiated requests. The MCP protocol is bidirectional, meaning servers
5//! can also send requests to clients for various purposes like elicitation,
6//! progress reporting, logging, and resource updates.
7//!
8//! ## Handler Types
9//!
10//! - **ElicitationHandler**: Handle user input requests from servers
11//! - **ProgressHandler**: Process progress notifications from long-running operations
12//! - **LogHandler**: Route server log messages to client logging systems
13//! - **ResourceUpdateHandler**: Handle notifications when resources change
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerError};
19//! use async_trait::async_trait;
20//!
21//! // Implement elicitation handler
22//! #[derive(Debug)]
23//! struct MyElicitationHandler;
24//!
25//! #[async_trait]
26//! impl ElicitationHandler for MyElicitationHandler {
27//! async fn handle_elicitation(
28//! &self,
29//! request: ElicitationRequest,
30//! ) -> Result<ElicitationResponse, HandlerError> {
31//! // Display the prompt to the user
32//! eprintln!("\n{}", request.prompt);
33//! eprintln!("---");
34//!
35//! // For each field in the schema, collect user input
36//! let mut content = std::collections::HashMap::new();
37//!
38//! if let Some(properties) = request.schema.get("properties") {
39//! if let Some(props) = properties.as_object() {
40//! for (field_name, field_schema) in props {
41//! let field_type = field_schema.get("type")
42//! .and_then(|v| v.as_str())
43//! .unwrap_or("string");
44//!
45//! eprint!("{} ({}): ", field_name, field_type);
46//!
47//! let mut input = String::new();
48//! std::io::stdin().read_line(&mut input)
49//! .map_err(|e| HandlerError::Generic {
50//! message: e.to_string()
51//! })?;
52//!
53//! let input = input.trim();
54//!
55//! // Parse input based on field type
56//! let value: serde_json::Value = match field_type {
57//! "boolean" => serde_json::json!(input == "true" || input == "yes" || input == "1"),
58//! "number" | "integer" => input.parse::<f64>()
59//! .map(|n| serde_json::json!(n))
60//! .unwrap_or_else(|_| serde_json::json!(input)),
61//! _ => serde_json::json!(input),
62//! };
63//!
64//! content.insert(field_name.clone(), value);
65//! }
66//! }
67//! }
68//!
69//! Ok(ElicitationResponse {
70//! action: ElicitationAction::Accept,
71//! content: Some(serde_json::to_value(content).unwrap()),
72//! })
73//! }
74//! }
75//! ```
76
77use async_trait::async_trait;
78use serde::{Deserialize, Serialize};
79use std::collections::HashMap;
80use std::sync::Arc;
81use thiserror::Error;
82use tracing::{debug, error, info, warn};
83use turbomcp_protocol::types::{
84 LogLevel, ProgressNotification as ProtocolProgressNotification, ResourceContents,
85};
86
87// ============================================================================
88// ERROR TYPES FOR HANDLER OPERATIONS
89// ============================================================================
90
91/// Errors that can occur during handler operations
92#[derive(Error, Debug)]
93#[non_exhaustive]
94pub enum HandlerError {
95 /// Handler operation failed due to user cancellation
96 #[error("User cancelled the operation")]
97 UserCancelled,
98
99 /// Handler operation timed out
100 #[error("Handler operation timed out after {timeout_seconds} seconds")]
101 Timeout { timeout_seconds: u64 },
102
103 /// Input validation failed
104 #[error("Invalid input: {details}")]
105 InvalidInput { details: String },
106
107 /// Handler configuration error
108 #[error("Handler configuration error: {message}")]
109 Configuration { message: String },
110
111 /// Generic handler error
112 #[error("Handler error: {message}")]
113 Generic { message: String },
114
115 /// External system error (e.g., UI framework, database)
116 #[error("External system error: {source}")]
117 External {
118 #[from]
119 source: Box<dyn std::error::Error + Send + Sync>,
120 },
121}
122
123pub type HandlerResult<T> = Result<T, HandlerError>;
124
125// ============================================================================
126// ELICITATION HANDLER TRAIT
127// ============================================================================
128
129/// Request structure for elicitation operations
130#[derive(Debug, Serialize, Deserialize, Clone)]
131pub struct ElicitationRequest {
132 /// Unique identifier for this elicitation request
133 pub id: String,
134
135 /// Human-readable prompt for the user
136 pub prompt: String,
137
138 /// JSON schema defining the expected response structure
139 pub schema: serde_json::Value,
140
141 /// Optional timeout in seconds
142 pub timeout: Option<u64>,
143
144 /// Additional metadata for the request
145 pub metadata: HashMap<String, serde_json::Value>,
146}
147
148/// Elicitation response action indicating user's choice
149#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
150#[serde(rename_all = "lowercase")]
151pub enum ElicitationAction {
152 /// User explicitly approved and submitted with data
153 Accept,
154 /// User explicitly declined the request
155 Decline,
156 /// User dismissed without making an explicit choice
157 Cancel,
158}
159
160/// Response structure for elicitation operations (MCP-compliant)
161#[derive(Debug, Serialize, Deserialize, Clone)]
162pub struct ElicitationResponse {
163 /// User's action choice (accept, decline, or cancel)
164 pub action: ElicitationAction,
165
166 /// User's response data (must conform to the request schema)
167 /// Only present for "accept" actions
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub content: Option<serde_json::Value>,
170}
171
172/// Handler for server-initiated elicitation requests
173///
174/// Elicitation is a mechanism where servers can request user input during
175/// operations. For example, a server might need user preferences, authentication
176/// credentials, or configuration choices to complete a task.
177///
178/// Implementations should:
179/// - Present the schema/prompt to the user in an appropriate UI
180/// - Validate user input against the provided schema
181/// - Handle user cancellation gracefully
182/// - Respect timeout constraints
183///
184/// # Examples
185///
186/// ```rust,no_run
187/// use turbomcp_client::handlers::{ElicitationAction, ElicitationHandler, ElicitationRequest, ElicitationResponse, HandlerResult};
188/// use async_trait::async_trait;
189/// use serde_json::json;
190///
191/// #[derive(Debug)]
192/// struct CLIElicitationHandler;
193///
194/// #[async_trait]
195/// impl ElicitationHandler for CLIElicitationHandler {
196/// async fn handle_elicitation(
197/// &self,
198/// request: ElicitationRequest,
199/// ) -> HandlerResult<ElicitationResponse> {
200/// println!("Server request: {}", request.prompt);
201///
202/// // In a real implementation, you would:
203/// // 1. Parse the schema to understand what input is needed
204/// // 2. Present an appropriate UI (CLI prompts, GUI forms, etc.)
205/// // 3. Validate the user's input against the schema
206/// // 4. Return the structured response
207///
208/// Ok(ElicitationResponse {
209/// action: ElicitationAction::Accept,
210/// content: Some(json!({ "user_choice": "example_value" })),
211/// })
212/// }
213/// }
214/// ```
215#[async_trait]
216pub trait ElicitationHandler: Send + Sync + std::fmt::Debug {
217 /// Handle an elicitation request from the server
218 ///
219 /// This method is called when a server needs user input. The implementation
220 /// should present the request to the user and collect their response.
221 ///
222 /// # Arguments
223 ///
224 /// * `request` - The elicitation request containing prompt, schema, and metadata
225 ///
226 /// # Returns
227 ///
228 /// Returns the user's response or an error if the operation failed.
229 async fn handle_elicitation(
230 &self,
231 request: ElicitationRequest,
232 ) -> HandlerResult<ElicitationResponse>;
233}
234
235// ============================================================================
236// PROGRESS HANDLER TRAIT
237// ============================================================================
238
239/// Progress notification from server operations
240#[derive(Debug, Serialize, Deserialize, Clone)]
241pub struct ProgressNotification {
242 /// Unique identifier for the operation being tracked
243 pub operation_id: String,
244
245 /// Current progress information
246 pub progress: ProtocolProgressNotification,
247
248 /// Human-readable status message
249 pub message: Option<String>,
250
251 /// Whether the operation has completed
252 pub completed: bool,
253
254 /// Optional error information if the operation failed
255 pub error: Option<String>,
256}
257
258/// Handler for server progress notifications
259///
260/// Progress handlers receive notifications about long-running server operations.
261/// This allows clients to display progress bars, status updates, or other
262/// feedback to users during operations that take significant time.
263///
264/// # Examples
265///
266/// ```rust,no_run
267/// use turbomcp_client::handlers::{ProgressHandler, ProgressNotification, HandlerResult};
268/// use async_trait::async_trait;
269///
270/// #[derive(Debug)]
271/// struct ProgressBarHandler;
272///
273/// #[async_trait]
274/// impl ProgressHandler for ProgressBarHandler {
275/// async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
276/// let progress_val = notification.progress.progress;
277/// if let Some(total) = notification.progress.total {
278/// let percentage = (progress_val / total) * 100.0;
279/// println!("Progress: {:.1}% - {}", percentage,
280/// notification.message.unwrap_or_default());
281/// } else {
282/// println!("Progress: {} - {}", progress_val,
283/// notification.message.unwrap_or_default());
284/// }
285///
286/// if notification.completed {
287/// if let Some(error) = notification.error {
288/// println!("Operation failed: {}", error);
289/// } else {
290/// println!("Operation completed successfully!");
291/// }
292/// }
293///
294/// Ok(())
295/// }
296/// }
297/// ```
298#[async_trait]
299pub trait ProgressHandler: Send + Sync + std::fmt::Debug {
300 /// Handle a progress notification from the server
301 ///
302 /// This method is called when the server sends progress updates for
303 /// long-running operations.
304 ///
305 /// # Arguments
306 ///
307 /// * `notification` - Progress information including current status and completion state
308 ///
309 /// # Returns
310 ///
311 /// Returns `Ok(())` if the notification was processed successfully.
312 async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()>;
313}
314
315// ============================================================================
316// LOG HANDLER TRAIT
317// ============================================================================
318
319/// Log message from the server
320#[derive(Debug, Serialize, Deserialize, Clone)]
321pub struct LogMessage {
322 /// Log level (Error, Warning, Info, Debug)
323 pub level: LogLevel,
324
325 /// The log message content
326 pub message: String,
327
328 /// Optional logger name/category
329 pub logger: Option<String>,
330
331 /// Timestamp when the log was created (ISO 8601 format)
332 pub timestamp: String,
333
334 /// Additional structured data
335 pub data: Option<serde_json::Value>,
336}
337
338/// Handler for server log messages
339///
340/// Log handlers receive log messages from the server and can route them to
341/// the client's logging system. This is useful for debugging, monitoring,
342/// and maintaining a unified log across client and server.
343///
344/// # Examples
345///
346/// ```rust,no_run
347/// use turbomcp_client::handlers::{LogHandler, LogMessage, HandlerResult};
348/// use turbomcp_protocol::types::LogLevel;
349/// use async_trait::async_trait;
350///
351/// #[derive(Debug)]
352/// struct TraceLogHandler;
353///
354/// #[async_trait]
355/// impl LogHandler for TraceLogHandler {
356/// async fn handle_log(&self, log: LogMessage) -> HandlerResult<()> {
357/// match log.level {
358/// LogLevel::Error => tracing::error!("Server: {}", log.message),
359/// LogLevel::Warning => tracing::warn!("Server: {}", log.message),
360/// LogLevel::Info => tracing::info!("Server: {}", log.message),
361/// LogLevel::Debug => tracing::debug!("Server: {}", log.message),
362/// LogLevel::Notice => tracing::info!("Server: {}", log.message),
363/// LogLevel::Critical => tracing::error!("Server CRITICAL: {}", log.message),
364/// LogLevel::Alert => tracing::error!("Server ALERT: {}", log.message),
365/// LogLevel::Emergency => tracing::error!("Server EMERGENCY: {}", log.message),
366/// }
367/// Ok(())
368/// }
369/// }
370/// ```
371#[async_trait]
372pub trait LogHandler: Send + Sync + std::fmt::Debug {
373 /// Handle a log message from the server
374 ///
375 /// This method is called when the server sends log messages to the client.
376 /// Implementations can route these to the client's logging system.
377 ///
378 /// # Arguments
379 ///
380 /// * `log` - The log message with level, content, and metadata
381 ///
382 /// # Returns
383 ///
384 /// Returns `Ok(())` if the log message was processed successfully.
385 async fn handle_log(&self, log: LogMessage) -> HandlerResult<()>;
386}
387
388// ============================================================================
389// RESOURCE UPDATE HANDLER TRAIT
390// ============================================================================
391
392/// Resource update notification
393#[derive(Debug, Serialize, Deserialize, Clone)]
394pub struct ResourceUpdateNotification {
395 /// URI of the resource that changed
396 pub uri: String,
397
398 /// Type of change (created, modified, deleted)
399 pub change_type: ResourceChangeType,
400
401 /// Updated resource content (for create/modify operations)
402 pub content: Option<ResourceContents>,
403
404 /// Timestamp of the change
405 pub timestamp: String,
406
407 /// Additional metadata about the change
408 pub metadata: HashMap<String, serde_json::Value>,
409}
410
411/// Types of resource changes
412#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
413#[serde(rename_all = "lowercase")]
414pub enum ResourceChangeType {
415 /// Resource was created
416 Created,
417 /// Resource was modified
418 Modified,
419 /// Resource was deleted
420 Deleted,
421}
422
423/// Handler for resource update notifications
424///
425/// Resource update handlers receive notifications when resources that the
426/// client has subscribed to are modified. This enables reactive updates
427/// to cached data or UI refreshes when server-side resources change.
428///
429/// # Examples
430///
431/// ```rust,no_run
432/// use turbomcp_client::handlers::{ResourceUpdateHandler, ResourceUpdateNotification, HandlerResult};
433/// use async_trait::async_trait;
434///
435/// #[derive(Debug)]
436/// struct CacheInvalidationHandler;
437///
438/// #[async_trait]
439/// impl ResourceUpdateHandler for CacheInvalidationHandler {
440/// async fn handle_resource_update(
441/// &self,
442/// notification: ResourceUpdateNotification,
443/// ) -> HandlerResult<()> {
444/// println!("Resource {} was {:?}",
445/// notification.uri,
446/// notification.change_type);
447///
448/// // In a real implementation, you might:
449/// // - Invalidate cached data for this resource
450/// // - Refresh UI components that display this resource
451/// // - Log the change for audit purposes
452/// // - Trigger dependent computations
453///
454/// Ok(())
455/// }
456/// }
457/// ```
458#[async_trait]
459pub trait ResourceUpdateHandler: Send + Sync + std::fmt::Debug {
460 /// Handle a resource update notification
461 ///
462 /// This method is called when a subscribed resource changes on the server.
463 ///
464 /// # Arguments
465 ///
466 /// * `notification` - Information about the resource change
467 ///
468 /// # Returns
469 ///
470 /// Returns `Ok(())` if the notification was processed successfully.
471 async fn handle_resource_update(
472 &self,
473 notification: ResourceUpdateNotification,
474 ) -> HandlerResult<()>;
475}
476
477// ============================================================================
478// ROOTS HANDLER TRAIT
479// ============================================================================
480
481/// Roots handler for responding to server requests for filesystem roots
482///
483/// Per MCP 2025-06-18 specification, `roots/list` is a SERVER->CLIENT request.
484/// Servers ask clients what filesystem roots (directories/files) they have access to.
485/// This is commonly used when servers need to understand their operating boundaries,
486/// such as which repositories or project directories they can access.
487///
488/// # Examples
489///
490/// ```rust,no_run
491/// use turbomcp_client::handlers::{RootsHandler, HandlerResult};
492/// use turbomcp_protocol::types::Root;
493/// use async_trait::async_trait;
494///
495/// #[derive(Debug)]
496/// struct MyRootsHandler {
497/// project_dirs: Vec<String>,
498/// }
499///
500/// #[async_trait]
501/// impl RootsHandler for MyRootsHandler {
502/// async fn handle_roots_request(&self) -> HandlerResult<Vec<Root>> {
503/// Ok(self.project_dirs
504/// .iter()
505/// .map(|dir| Root {
506/// uri: format!("file://{}", dir).into(),
507/// name: Some(dir.split('/').last().unwrap_or("").to_string()),
508/// })
509/// .collect())
510/// }
511/// }
512/// ```
513#[async_trait]
514pub trait RootsHandler: Send + Sync + std::fmt::Debug {
515 /// Handle a roots/list request from the server
516 ///
517 /// This method is called when the server wants to know which filesystem roots
518 /// the client has available. The implementation should return a list of Root
519 /// objects representing directories or files the server can operate on.
520 ///
521 /// # Returns
522 ///
523 /// Returns a vector of Root objects, each with a URI (must start with file://)
524 /// and optional human-readable name.
525 ///
526 /// # Note
527 ///
528 /// Per MCP specification, URIs must start with `file://` for now. This restriction
529 /// may be relaxed in future protocol versions.
530 async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>>;
531}
532
533// ============================================================================
534// HANDLER REGISTRY FOR CLIENT
535// ============================================================================
536
537/// Registry for managing client-side handlers
538///
539/// This registry holds all the handler implementations and provides methods
540/// for registering and invoking them. It's used internally by the Client
541/// to dispatch server-initiated requests to the appropriate handlers.
542#[derive(Debug, Default)]
543pub struct HandlerRegistry {
544 /// Roots handler for filesystem root requests
545 pub roots: Option<Arc<dyn RootsHandler>>,
546
547 /// Elicitation handler for user input requests
548 pub elicitation: Option<Arc<dyn ElicitationHandler>>,
549
550 /// Progress handler for operation updates
551 pub progress: Option<Arc<dyn ProgressHandler>>,
552
553 /// Log handler for server log messages
554 pub log: Option<Arc<dyn LogHandler>>,
555
556 /// Resource update handler for resource change notifications
557 pub resource_update: Option<Arc<dyn ResourceUpdateHandler>>,
558}
559
560impl HandlerRegistry {
561 /// Create a new empty handler registry
562 pub fn new() -> Self {
563 Self::default()
564 }
565
566 /// Register a roots handler
567 pub fn set_roots_handler(&mut self, handler: Arc<dyn RootsHandler>) {
568 debug!("Registering roots handler");
569 self.roots = Some(handler);
570 }
571
572 /// Register an elicitation handler
573 pub fn set_elicitation_handler(&mut self, handler: Arc<dyn ElicitationHandler>) {
574 debug!("Registering elicitation handler");
575 self.elicitation = Some(handler);
576 }
577
578 /// Register a progress handler
579 pub fn set_progress_handler(&mut self, handler: Arc<dyn ProgressHandler>) {
580 debug!("Registering progress handler");
581 self.progress = Some(handler);
582 }
583
584 /// Register a log handler
585 pub fn set_log_handler(&mut self, handler: Arc<dyn LogHandler>) {
586 debug!("Registering log handler");
587 self.log = Some(handler);
588 }
589
590 /// Register a resource update handler
591 pub fn set_resource_update_handler(&mut self, handler: Arc<dyn ResourceUpdateHandler>) {
592 debug!("Registering resource update handler");
593 self.resource_update = Some(handler);
594 }
595
596 /// Check if a roots handler is registered
597 pub fn has_roots_handler(&self) -> bool {
598 self.roots.is_some()
599 }
600
601 /// Check if an elicitation handler is registered
602 pub fn has_elicitation_handler(&self) -> bool {
603 self.elicitation.is_some()
604 }
605
606 /// Check if a progress handler is registered
607 pub fn has_progress_handler(&self) -> bool {
608 self.progress.is_some()
609 }
610
611 /// Check if a log handler is registered
612 pub fn has_log_handler(&self) -> bool {
613 self.log.is_some()
614 }
615
616 /// Check if a resource update handler is registered
617 pub fn has_resource_update_handler(&self) -> bool {
618 self.resource_update.is_some()
619 }
620
621 /// Handle a roots/list request from the server
622 pub async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>> {
623 match &self.roots {
624 Some(handler) => {
625 info!("Processing roots/list request from server");
626 handler.handle_roots_request().await
627 }
628 None => {
629 warn!("No roots handler registered, returning empty roots list");
630 // Return empty list per MCP spec - client has no roots available
631 Ok(Vec::new())
632 }
633 }
634 }
635
636 /// Handle an elicitation request
637 pub async fn handle_elicitation(
638 &self,
639 request: ElicitationRequest,
640 ) -> HandlerResult<ElicitationResponse> {
641 match &self.elicitation {
642 Some(handler) => {
643 info!("Processing elicitation request: {}", request.id);
644 handler.handle_elicitation(request).await
645 }
646 None => {
647 warn!("No elicitation handler registered, declining request");
648 Err(HandlerError::Configuration {
649 message: "No elicitation handler registered".to_string(),
650 })
651 }
652 }
653 }
654
655 /// Handle a progress notification
656 pub async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
657 match &self.progress {
658 Some(handler) => {
659 debug!(
660 "Processing progress notification: {}",
661 notification.operation_id
662 );
663 handler.handle_progress(notification).await
664 }
665 None => {
666 debug!("No progress handler registered, ignoring notification");
667 Ok(())
668 }
669 }
670 }
671
672 /// Handle a log message
673 pub async fn handle_log(&self, log: LogMessage) -> HandlerResult<()> {
674 match &self.log {
675 Some(handler) => handler.handle_log(log).await,
676 None => {
677 debug!("No log handler registered, ignoring log message");
678 Ok(())
679 }
680 }
681 }
682
683 /// Handle a resource update notification
684 pub async fn handle_resource_update(
685 &self,
686 notification: ResourceUpdateNotification,
687 ) -> HandlerResult<()> {
688 match &self.resource_update {
689 Some(handler) => {
690 debug!("Processing resource update: {}", notification.uri);
691 handler.handle_resource_update(notification).await
692 }
693 None => {
694 debug!("No resource update handler registered, ignoring notification");
695 Ok(())
696 }
697 }
698 }
699}
700
701// ============================================================================
702// DEFAULT HANDLER IMPLEMENTATIONS
703// ============================================================================
704
705/// Default elicitation handler that declines all requests
706#[derive(Debug)]
707pub struct DeclineElicitationHandler;
708
709#[async_trait]
710impl ElicitationHandler for DeclineElicitationHandler {
711 async fn handle_elicitation(
712 &self,
713 request: ElicitationRequest,
714 ) -> HandlerResult<ElicitationResponse> {
715 warn!("Declining elicitation request: {}", request.prompt);
716 Ok(ElicitationResponse {
717 action: ElicitationAction::Decline,
718 content: None,
719 })
720 }
721}
722
723/// Default progress handler that logs progress to tracing
724#[derive(Debug)]
725pub struct LoggingProgressHandler;
726
727#[async_trait]
728impl ProgressHandler for LoggingProgressHandler {
729 async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
730 if notification.completed {
731 if let Some(error) = ¬ification.error {
732 error!("Operation {} failed: {}", notification.operation_id, error);
733 } else {
734 info!(
735 "Operation {} completed successfully",
736 notification.operation_id
737 );
738 }
739 } else if let Some(message) = ¬ification.message {
740 info!("Operation {}: {}", notification.operation_id, message);
741 }
742
743 Ok(())
744 }
745}
746
747/// Default log handler that routes server logs to tracing
748#[derive(Debug)]
749pub struct TracingLogHandler;
750
751#[async_trait]
752impl LogHandler for TracingLogHandler {
753 async fn handle_log(&self, log: LogMessage) -> HandlerResult<()> {
754 let logger_prefix = log.logger.as_deref().unwrap_or("server");
755
756 match log.level {
757 LogLevel::Error => error!("[{}] {}", logger_prefix, log.message),
758 LogLevel::Warning => warn!("[{}] {}", logger_prefix, log.message),
759 LogLevel::Info => info!("[{}] {}", logger_prefix, log.message),
760 LogLevel::Debug => debug!("[{}] {}", logger_prefix, log.message),
761 LogLevel::Notice => info!("[{}] [NOTICE] {}", logger_prefix, log.message),
762 LogLevel::Critical => error!("[{}] [CRITICAL] {}", logger_prefix, log.message),
763 LogLevel::Alert => error!("[{}] [ALERT] {}", logger_prefix, log.message),
764 LogLevel::Emergency => error!("[{}] [EMERGENCY] {}", logger_prefix, log.message),
765 }
766
767 Ok(())
768 }
769}
770
771/// Default resource update handler that logs changes
772#[derive(Debug)]
773pub struct LoggingResourceUpdateHandler;
774
775#[async_trait]
776impl ResourceUpdateHandler for LoggingResourceUpdateHandler {
777 async fn handle_resource_update(
778 &self,
779 notification: ResourceUpdateNotification,
780 ) -> HandlerResult<()> {
781 info!(
782 "Resource {} was {:?} at {}",
783 notification.uri, notification.change_type, notification.timestamp
784 );
785 Ok(())
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792 use serde_json::json;
793 use tokio;
794
795 // Test handler implementations
796 #[derive(Debug)]
797 struct TestElicitationHandler;
798
799 #[async_trait]
800 impl ElicitationHandler for TestElicitationHandler {
801 async fn handle_elicitation(
802 &self,
803 _request: ElicitationRequest,
804 ) -> HandlerResult<ElicitationResponse> {
805 Ok(ElicitationResponse {
806 action: ElicitationAction::Accept,
807 content: Some(json!({"test": "response"})),
808 })
809 }
810 }
811
812 #[derive(Debug)]
813 struct TestProgressHandler;
814
815 #[async_trait]
816 impl ProgressHandler for TestProgressHandler {
817 async fn handle_progress(&self, _notification: ProgressNotification) -> HandlerResult<()> {
818 Ok(())
819 }
820 }
821
822 #[tokio::test]
823 async fn test_handler_registry_creation() {
824 let registry = HandlerRegistry::new();
825 assert!(!registry.has_elicitation_handler());
826 assert!(!registry.has_progress_handler());
827 assert!(!registry.has_log_handler());
828 assert!(!registry.has_resource_update_handler());
829 }
830
831 #[tokio::test]
832 async fn test_elicitation_handler_registration() {
833 let mut registry = HandlerRegistry::new();
834 let handler = Arc::new(TestElicitationHandler);
835
836 registry.set_elicitation_handler(handler);
837 assert!(registry.has_elicitation_handler());
838 }
839
840 #[tokio::test]
841 async fn test_elicitation_request_handling() {
842 let mut registry = HandlerRegistry::new();
843 let handler = Arc::new(TestElicitationHandler);
844 registry.set_elicitation_handler(handler);
845
846 let request = ElicitationRequest {
847 id: "test-123".to_string(),
848 prompt: "Test prompt".to_string(),
849 schema: json!({"type": "object"}),
850 timeout: None,
851 metadata: HashMap::new(),
852 };
853
854 let response = registry.handle_elicitation(request).await.unwrap();
855 assert_eq!(response.action, ElicitationAction::Accept);
856 assert!(response.content.is_some());
857 }
858
859 #[tokio::test]
860 async fn test_progress_handler_registration() {
861 let mut registry = HandlerRegistry::new();
862 let handler = Arc::new(TestProgressHandler);
863
864 registry.set_progress_handler(handler);
865 assert!(registry.has_progress_handler());
866 }
867
868 #[tokio::test]
869 async fn test_default_handlers() {
870 let decline_handler = DeclineElicitationHandler;
871 let request = ElicitationRequest {
872 id: "test".to_string(),
873 prompt: "Test".to_string(),
874 schema: json!({}),
875 timeout: None,
876 metadata: HashMap::new(),
877 };
878
879 let response = decline_handler.handle_elicitation(request).await.unwrap();
880 assert_eq!(response.action, ElicitationAction::Decline);
881 }
882
883 #[tokio::test]
884 async fn test_handler_error_types() {
885 let error = HandlerError::UserCancelled;
886 assert!(error.to_string().contains("User cancelled"));
887
888 let timeout_error = HandlerError::Timeout {
889 timeout_seconds: 30,
890 };
891 assert!(timeout_error.to_string().contains("30 seconds"));
892 }
893}