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//! logging, and resource updates.
7//!
8//! ## Handler Types
9//!
10//! - **ElicitationHandler**: Handle user input requests from servers
11//! - **LogHandler**: Route server log messages to client logging systems
12//! - **ResourceUpdateHandler**: Handle notifications when resources change
13//!
14//! ## Usage
15//!
16//! ```rust,no_run
17//! use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerError};
18//! use async_trait::async_trait;
19//!
20//! // Implement elicitation handler
21//! #[derive(Debug)]
22//! struct MyElicitationHandler;
23//!
24//! #[async_trait]
25//! impl ElicitationHandler for MyElicitationHandler {
26//!     async fn handle_elicitation(
27//!         &self,
28//!         request: ElicitationRequest,
29//!     ) -> Result<ElicitationResponse, HandlerError> {
30//!         // Display the prompt to the user
31//!         eprintln!("\n{}", request.message());
32//!         eprintln!("---");
33//!
34//!         // Access the typed schema (not serde_json::Value!)
35//!         let mut content = std::collections::HashMap::new();
36//!         if let Some(schema) = request.schema() {
37//!             for (field_name, field_def) in &schema.properties {
38//!                 eprint!("{}: ", field_name);
39//!
40//!                 let mut input = String::new();
41//!                 std::io::stdin().read_line(&mut input)
42//!                     .map_err(|e| HandlerError::Generic {
43//!                         message: e.to_string()
44//!                     })?;
45//!
46//!                 let input = input.trim();
47//!
48//!                 // Parse input based on field type (from typed schema!)
49//!                 use turbomcp_protocol::types::PrimitiveSchemaDefinition;
50//!                 let value: serde_json::Value = match field_def {
51//!                     PrimitiveSchemaDefinition::Boolean { .. } => {
52//!                         serde_json::json!(input == "true" || input == "yes" || input == "1")
53//!                     }
54//!                     PrimitiveSchemaDefinition::Number { .. } | PrimitiveSchemaDefinition::Integer { .. } => {
55//!                         input.parse::<f64>()
56//!                             .map(|n| serde_json::json!(n))
57//!                             .unwrap_or_else(|_| serde_json::json!(input))
58//!                     }
59//!                     _ => serde_json::json!(input),
60//!                 };
61//!
62//!                 content.insert(field_name.clone(), value);
63//!             }
64//!         }
65//!
66//!         Ok(ElicitationResponse::accept(content))
67//!     }
68//! }
69//! ```
70
71use async_trait::async_trait;
72use std::collections::HashMap;
73use std::sync::Arc;
74use std::time::Duration;
75use thiserror::Error;
76use tracing::{debug, error, info, warn};
77use turbomcp_protocol::MessageId;
78use turbomcp_protocol::jsonrpc::JsonRpcError;
79use turbomcp_protocol::types::LogLevel;
80
81// Re-export MCP protocol notification types directly (MCP spec compliance)
82pub use turbomcp_protocol::types::{
83    CancelledNotification,       // MCP 2025-06-18 spec
84    LoggingNotification,         // MCP 2025-06-18 spec
85    ResourceUpdatedNotification, // MCP 2025-06-18 spec
86};
87
88// ============================================================================
89// ERROR TYPES FOR HANDLER OPERATIONS
90// ============================================================================
91
92/// Errors that can occur during handler operations
93#[derive(Error, Debug)]
94#[non_exhaustive]
95pub enum HandlerError {
96    /// Handler operation failed due to user cancellation
97    #[error("User cancelled the operation")]
98    UserCancelled,
99
100    /// Handler operation timed out
101    #[error("Handler operation timed out after {timeout_seconds} seconds")]
102    Timeout { timeout_seconds: u64 },
103
104    /// Input validation failed
105    #[error("Invalid input: {details}")]
106    InvalidInput { details: String },
107
108    /// Handler configuration error
109    #[error("Handler configuration error: {message}")]
110    Configuration { message: String },
111
112    /// Generic handler error
113    #[error("Handler error: {message}")]
114    Generic { message: String },
115
116    /// External system error (e.g., UI framework, database)
117    #[error("External system error: {source}")]
118    External {
119        #[from]
120        source: Box<dyn std::error::Error + Send + Sync>,
121    },
122}
123
124impl HandlerError {
125    /// Convert handler error to JSON-RPC error
126    ///
127    /// This method centralizes the mapping between handler errors and
128    /// JSON-RPC error codes, ensuring consistency across all handlers.
129    ///
130    /// # Error Code Mapping
131    ///
132    /// - **-1**: User rejected sampling request (MCP 2025-06-18 spec)
133    /// - **-32801**: Handler operation timed out
134    /// - **-32602**: Invalid input (bad request)
135    /// - **-32601**: Handler configuration error (method not found)
136    /// - **-32603**: Generic/external handler error (internal error)
137    ///
138    /// # Examples
139    ///
140    /// ```rust
141    /// use turbomcp_client::handlers::HandlerError;
142    ///
143    /// let error = HandlerError::UserCancelled;
144    /// let jsonrpc_error = error.into_jsonrpc_error();
145    /// assert_eq!(jsonrpc_error.code, -1);
146    /// assert!(jsonrpc_error.message.contains("User rejected"));
147    /// ```
148    #[must_use]
149    pub fn into_jsonrpc_error(&self) -> JsonRpcError {
150        let (code, message) = match self {
151            HandlerError::UserCancelled => (-1, "User rejected sampling request".to_string()),
152            HandlerError::Timeout { timeout_seconds } => (
153                -32801,
154                format!(
155                    "Handler operation timed out after {} seconds",
156                    timeout_seconds
157                ),
158            ),
159            HandlerError::InvalidInput { details } => {
160                (-32602, format!("Invalid input: {}", details))
161            }
162            HandlerError::Configuration { message } => {
163                (-32601, format!("Handler configuration error: {}", message))
164            }
165            HandlerError::Generic { message } => (-32603, format!("Handler error: {}", message)),
166            HandlerError::External { source } => {
167                (-32603, format!("External system error: {}", source))
168            }
169        };
170
171        JsonRpcError {
172            code,
173            message,
174            data: None,
175        }
176    }
177}
178
179pub type HandlerResult<T> = Result<T, HandlerError>;
180
181// ============================================================================
182// ELICITATION HANDLER TRAIT
183// ============================================================================
184
185/// Ergonomic wrapper around protocol ElicitRequest with request ID
186///
187/// This type wraps the protocol-level `ElicitRequest` and adds the request ID
188/// from the JSON-RPC envelope. It provides ergonomic accessors while preserving
189/// full type safety from the protocol layer.
190///
191/// # Design Philosophy
192///
193/// Rather than duplicating protocol types, we wrap them. This ensures:
194/// - Type safety is preserved (ElicitationSchema stays typed!)
195/// - No data loss (Duration instead of lossy integer seconds)
196/// - Single source of truth (protocol crate defines MCP types)
197/// - Automatic sync (protocol changes propagate automatically)
198///
199/// # Examples
200///
201/// ```rust,no_run
202/// use turbomcp_client::handlers::ElicitationRequest;
203///
204/// async fn handle(request: ElicitationRequest) {
205///     // Access request ID
206///     println!("ID: {:?}", request.id());
207///
208///     // Access message
209///     println!("Message: {}", request.message());
210///
211///     // Access typed schema (not Value!)
212///     if let Some(schema) = request.schema() {
213///         for (name, property) in &schema.properties {
214///             println!("Field: {}", name);
215///         }
216///     }
217///
218///     // Access timeout as Duration
219///     if let Some(timeout) = request.timeout() {
220///         println!("Timeout: {:?}", timeout);
221///     }
222/// }
223/// ```
224#[derive(Debug, Clone)]
225pub struct ElicitationRequest {
226    id: MessageId,
227    inner: turbomcp_protocol::types::ElicitRequest,
228}
229
230impl ElicitationRequest {
231    /// Create a new elicitation request wrapper
232    ///
233    /// # Arguments
234    ///
235    /// * `id` - Request ID from JSON-RPC envelope
236    /// * `request` - Protocol-level elicit request
237    #[must_use]
238    pub fn new(id: MessageId, request: turbomcp_protocol::types::ElicitRequest) -> Self {
239        Self { id, inner: request }
240    }
241
242    /// Get request ID from JSON-RPC envelope
243    #[must_use]
244    pub fn id(&self) -> &MessageId {
245        &self.id
246    }
247
248    /// Get human-readable message for the user
249    ///
250    /// This is the primary prompt/question being asked of the user.
251    #[must_use]
252    pub fn message(&self) -> &str {
253        self.inner.params.message()
254    }
255
256    /// Get schema defining expected response structure
257    ///
258    /// Returns the typed `ElicitationSchema` which provides:
259    /// - Type-safe access to properties
260    /// - Required field information
261    /// - Validation constraints
262    ///
263    /// # Note
264    ///
265    /// This returns a TYPED schema, not `serde_json::Value`.
266    /// You can inspect the schema structure type-safely:
267    ///
268    /// ```rust,no_run
269    /// # use turbomcp_client::handlers::ElicitationRequest;
270    /// # use turbomcp_protocol::types::PrimitiveSchemaDefinition;
271    /// # async fn example(request: ElicitationRequest) {
272    /// if let Some(schema) = request.schema() {
273    ///     for (name, definition) in &schema.properties {
274    ///         match definition {
275    ///             PrimitiveSchemaDefinition::String { description, .. } => {
276    ///                 println!("String field: {}", name);
277    ///             }
278    ///             PrimitiveSchemaDefinition::Number { minimum, maximum, .. } => {
279    ///                 println!("Number field: {} ({:?}-{:?})", name, minimum, maximum);
280    ///             }
281    ///             _ => {}
282    ///         }
283    ///     }
284    /// }
285    /// # }
286    /// ```
287    #[must_use]
288    pub fn schema(&self) -> Option<&turbomcp_protocol::types::ElicitationSchema> {
289        #[allow(unreachable_patterns)]
290        match &self.inner.params {
291            turbomcp_protocol::types::ElicitRequestParams::Form(form) => Some(&form.schema),
292            _ => None, // URL elicitation (when mcp-url-elicitation feature is enabled)
293        }
294    }
295
296    /// Get optional timeout as Duration
297    ///
298    /// Converts milliseconds from the protocol to ergonomic `Duration` type.
299    /// No data loss occurs (unlike converting to integer seconds).
300    #[must_use]
301    pub fn timeout(&self) -> Option<Duration> {
302        #[allow(unreachable_patterns)]
303        match &self.inner.params {
304            turbomcp_protocol::types::ElicitRequestParams::Form(form) => {
305                form.timeout_ms.map(|ms| Duration::from_millis(ms as u64))
306            }
307            _ => None, // URL elicitation (when mcp-url-elicitation feature is enabled)
308        }
309    }
310
311    /// Check if request can be cancelled by the user
312    #[must_use]
313    pub fn is_cancellable(&self) -> bool {
314        #[allow(unreachable_patterns)]
315        match &self.inner.params {
316            turbomcp_protocol::types::ElicitRequestParams::Form(form) => {
317                form.cancellable.unwrap_or(false)
318            }
319            _ => false, // URL elicitation (when mcp-url-elicitation feature is enabled)
320        }
321    }
322
323    /// Get access to underlying protocol request if needed
324    ///
325    /// For advanced use cases where you need the raw protocol type.
326    #[must_use]
327    pub fn as_protocol(&self) -> &turbomcp_protocol::types::ElicitRequest {
328        &self.inner
329    }
330
331    /// Consume wrapper and return protocol request
332    #[must_use]
333    pub fn into_protocol(self) -> turbomcp_protocol::types::ElicitRequest {
334        self.inner
335    }
336}
337
338// Re-export protocol action enum (no need to duplicate)
339pub use turbomcp_protocol::types::ElicitationAction;
340
341/// Elicitation response builder
342///
343/// Wrapper around protocol `ElicitResult` with ergonomic factory methods.
344///
345/// # Examples
346///
347/// ```rust
348/// use turbomcp_client::handlers::ElicitationResponse;
349/// use std::collections::HashMap;
350///
351/// // Accept with content
352/// let mut content = HashMap::new();
353/// content.insert("name".to_string(), serde_json::json!("Alice"));
354/// let response = ElicitationResponse::accept(content);
355///
356/// // Decline
357/// let response = ElicitationResponse::decline();
358///
359/// // Cancel
360/// let response = ElicitationResponse::cancel();
361/// ```
362#[derive(Debug, Clone)]
363pub struct ElicitationResponse {
364    inner: turbomcp_protocol::types::ElicitResult,
365}
366
367impl ElicitationResponse {
368    /// Create response with accept action and user content
369    ///
370    /// # Arguments
371    ///
372    /// * `content` - User-submitted data conforming to the request schema
373    #[must_use]
374    pub fn accept(content: HashMap<String, serde_json::Value>) -> Self {
375        Self {
376            inner: turbomcp_protocol::types::ElicitResult {
377                action: ElicitationAction::Accept,
378                content: Some(content),
379                _meta: None,
380            },
381        }
382    }
383
384    /// Create response with decline action (user explicitly declined)
385    #[must_use]
386    pub fn decline() -> Self {
387        Self {
388            inner: turbomcp_protocol::types::ElicitResult {
389                action: ElicitationAction::Decline,
390                content: None,
391                _meta: None,
392            },
393        }
394    }
395
396    /// Create response with cancel action (user dismissed without choice)
397    #[must_use]
398    pub fn cancel() -> Self {
399        Self {
400            inner: turbomcp_protocol::types::ElicitResult {
401                action: ElicitationAction::Cancel,
402                content: None,
403                _meta: None,
404            },
405        }
406    }
407
408    /// Get the action from this response
409    #[must_use]
410    pub fn action(&self) -> ElicitationAction {
411        self.inner.action
412    }
413
414    /// Get the content from this response
415    #[must_use]
416    pub fn content(&self) -> Option<&HashMap<String, serde_json::Value>> {
417        self.inner.content.as_ref()
418    }
419
420    /// Convert to protocol type for sending over the wire
421    pub(crate) fn into_protocol(self) -> turbomcp_protocol::types::ElicitResult {
422        self.inner
423    }
424}
425
426/// Handler for server-initiated elicitation requests
427///
428/// Elicitation is a mechanism where servers can request user input during
429/// operations. For example, a server might need user preferences, authentication
430/// credentials, or configuration choices to complete a task.
431///
432/// Implementations should:
433/// - Present the schema/prompt to the user in an appropriate UI
434/// - Validate user input against the provided schema
435/// - Handle user cancellation gracefully
436/// - Respect timeout constraints
437///
438/// # Examples
439///
440/// ```rust,no_run
441/// use turbomcp_client::handlers::{ElicitationAction, ElicitationHandler, ElicitationRequest, ElicitationResponse, HandlerResult};
442/// use async_trait::async_trait;
443/// use serde_json::json;
444///
445/// #[derive(Debug)]
446/// struct CLIElicitationHandler;
447///
448/// #[async_trait]
449/// impl ElicitationHandler for CLIElicitationHandler {
450///     async fn handle_elicitation(
451///         &self,
452///         request: ElicitationRequest,
453///     ) -> HandlerResult<ElicitationResponse> {
454///         println!("Server request: {}", request.message());
455///
456///         // In a real implementation, you would:
457///         // 1. Inspect the typed schema to understand what input is needed
458///         // 2. Present an appropriate UI (CLI prompts, GUI forms, etc.)
459///         // 3. Validate the user's input against the schema
460///         // 4. Return the structured response
461///
462///         let mut content = std::collections::HashMap::new();
463///         content.insert("user_choice".to_string(), json!("example_value"));
464///         Ok(ElicitationResponse::accept(content))
465///     }
466/// }
467/// ```
468#[async_trait]
469pub trait ElicitationHandler: Send + Sync + std::fmt::Debug {
470    /// Handle an elicitation request from the server
471    ///
472    /// This method is called when a server needs user input. The implementation
473    /// should present the request to the user and collect their response.
474    ///
475    /// # Arguments
476    ///
477    /// * `request` - The elicitation request containing prompt, schema, and metadata
478    ///
479    /// # Returns
480    ///
481    /// Returns the user's response or an error if the operation failed.
482    async fn handle_elicitation(
483        &self,
484        request: ElicitationRequest,
485    ) -> HandlerResult<ElicitationResponse>;
486}
487
488// ============================================================================
489
490// ============================================================================
491// LOG HANDLER TRAIT
492// ============================================================================
493
494// LoggingNotification is re-exported from protocol (see imports above)
495// This ensures MCP 2025-06-18 spec compliance
496
497/// Handler for server log messages
498///
499/// Log handlers receive log messages from the server and can route them to
500/// the client's logging system. This is useful for debugging, monitoring,
501/// and maintaining a unified log across client and server.
502///
503/// # Examples
504///
505/// ```rust,no_run
506/// use turbomcp_client::handlers::{LogHandler, LoggingNotification, HandlerResult};
507/// use turbomcp_protocol::types::LogLevel;
508/// use async_trait::async_trait;
509///
510/// #[derive(Debug)]
511/// struct TraceLogHandler;
512///
513/// #[async_trait]
514/// impl LogHandler for TraceLogHandler {
515///     async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
516///         // MCP spec: data can be any JSON type (string, object, etc.)
517///         let message = log.data.to_string();
518///         match log.level {
519///             LogLevel::Error => tracing::error!("Server: {}", message),
520///             LogLevel::Warning => tracing::warn!("Server: {}", message),
521///             LogLevel::Info => tracing::info!("Server: {}", message),
522///             LogLevel::Debug => tracing::debug!("Server: {}", message),
523///             LogLevel::Notice => tracing::info!("Server: {}", message),
524///             LogLevel::Critical => tracing::error!("Server CRITICAL: {}", message),
525///             LogLevel::Alert => tracing::error!("Server ALERT: {}", message),
526///             LogLevel::Emergency => tracing::error!("Server EMERGENCY: {}", message),
527///         }
528///         Ok(())
529///     }
530/// }
531/// ```
532#[async_trait]
533pub trait LogHandler: Send + Sync + std::fmt::Debug {
534    /// Handle a log message from the server
535    ///
536    /// This method is called when the server sends log messages to the client.
537    /// Implementations can route these to the client's logging system.
538    ///
539    /// # Arguments
540    ///
541    /// * `log` - The log notification with level and data (per MCP 2025-06-18 spec)
542    ///
543    /// # Returns
544    ///
545    /// Returns `Ok(())` if the log message was processed successfully.
546    async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()>;
547}
548
549// ============================================================================
550// RESOURCE UPDATE HANDLER TRAIT
551// ============================================================================
552
553// ResourceUpdatedNotification is re-exported from protocol (see imports above)
554// This ensures MCP 2025-06-18 spec compliance
555//
556// Per MCP spec: This notification ONLY contains the URI of the changed resource.
557// Clients must call resources/read to get the updated content.
558
559/// Handler for resource update notifications
560///
561/// Resource update handlers receive notifications when resources that the
562/// client has subscribed to are modified. This enables reactive updates
563/// to cached data or UI refreshes when server-side resources change.
564///
565/// # Examples
566///
567/// ```rust,no_run
568/// use turbomcp_client::handlers::{ResourceUpdateHandler, ResourceUpdatedNotification, HandlerResult};
569/// use async_trait::async_trait;
570///
571/// #[derive(Debug)]
572/// struct CacheInvalidationHandler;
573///
574/// #[async_trait]
575/// impl ResourceUpdateHandler for CacheInvalidationHandler {
576///     async fn handle_resource_update(
577///         &self,
578///         notification: ResourceUpdatedNotification,
579///     ) -> HandlerResult<()> {
580///         // Per MCP spec: notification only contains URI
581///         // Client must call resources/read to get updated content
582///         println!("Resource {} was updated", notification.uri);
583///
584///         // In a real implementation, you might:
585///         // - Invalidate cached data for this resource
586///         // - Refresh UI components that display this resource
587///         // - Log the change for audit purposes
588///         // - Trigger dependent computations
589///         
590///         Ok(())
591///     }
592/// }
593/// ```
594#[async_trait]
595pub trait ResourceUpdateHandler: Send + Sync + std::fmt::Debug {
596    /// Handle a resource update notification
597    ///
598    /// This method is called when a subscribed resource changes on the server.
599    ///
600    /// # Arguments
601    ///
602    /// * `notification` - Information about the resource change
603    ///
604    /// # Returns
605    ///
606    /// Returns `Ok(())` if the notification was processed successfully.
607    async fn handle_resource_update(
608        &self,
609        notification: ResourceUpdatedNotification,
610    ) -> HandlerResult<()>;
611}
612
613// ============================================================================
614// ROOTS HANDLER TRAIT
615// ============================================================================
616
617/// Roots handler for responding to server requests for filesystem roots
618///
619/// Per MCP 2025-06-18 specification, `roots/list` is a SERVER->CLIENT request.
620/// Servers ask clients what filesystem roots (directories/files) they have access to.
621/// This is commonly used when servers need to understand their operating boundaries,
622/// such as which repositories or project directories they can access.
623///
624/// # Examples
625///
626/// ```rust,no_run
627/// use turbomcp_client::handlers::{RootsHandler, HandlerResult};
628/// use turbomcp_protocol::types::Root;
629/// use async_trait::async_trait;
630///
631/// #[derive(Debug)]
632/// struct MyRootsHandler {
633///     project_dirs: Vec<String>,
634/// }
635///
636/// #[async_trait]
637/// impl RootsHandler for MyRootsHandler {
638///     async fn handle_roots_request(&self) -> HandlerResult<Vec<Root>> {
639///         Ok(self.project_dirs
640///             .iter()
641///             .map(|dir| Root {
642///                 uri: format!("file://{}", dir).into(),
643///                 name: Some(dir.split('/').last().unwrap_or("").to_string()),
644///             })
645///             .collect())
646///     }
647/// }
648/// ```
649#[async_trait]
650pub trait RootsHandler: Send + Sync + std::fmt::Debug {
651    /// Handle a roots/list request from the server
652    ///
653    /// This method is called when the server wants to know which filesystem roots
654    /// the client has available. The implementation should return a list of Root
655    /// objects representing directories or files the server can operate on.
656    ///
657    /// # Returns
658    ///
659    /// Returns a vector of Root objects, each with a URI (must start with file://)
660    /// and optional human-readable name.
661    ///
662    /// # Note
663    ///
664    /// Per MCP specification, URIs must start with `file://` for now. This restriction
665    /// may be relaxed in future protocol versions.
666    async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>>;
667}
668
669// ============================================================================
670// CANCELLATION HANDLER TRAIT
671// ============================================================================
672
673/// Cancellation handler for processing cancellation notifications
674///
675/// Per MCP 2025-06-18 specification, `notifications/cancelled` can be sent by
676/// either side to indicate cancellation of a previously-issued request.
677///
678/// When the server sends a cancellation notification, it indicates that a request
679/// the client sent is being cancelled and the result will be unused. The client
680/// SHOULD cease any associated processing.
681///
682/// # MCP Specification
683///
684/// From the MCP spec:
685/// - "The request SHOULD still be in-flight, but due to communication latency,
686///    it is always possible that this notification MAY arrive after the request
687///    has already finished."
688/// - "A client MUST NOT attempt to cancel its `initialize` request."
689///
690/// # Examples
691///
692/// ```rust,no_run
693/// use turbomcp_client::handlers::{CancellationHandler, CancelledNotification, HandlerResult};
694/// use async_trait::async_trait;
695///
696/// #[derive(Debug)]
697/// struct MyCancellationHandler;
698///
699/// #[async_trait]
700/// impl CancellationHandler for MyCancellationHandler {
701///     async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()> {
702///         println!("Request {} was cancelled", notification.request_id);
703///         if let Some(reason) = &notification.reason {
704///             println!("Reason: {}", reason);
705///         }
706///
707///         // In a real implementation:
708///         // - Look up the in-flight request by notification.request_id
709///         // - Signal cancellation (e.g., via CancellationToken)
710///         // - Clean up any resources
711///
712///         Ok(())
713///     }
714/// }
715/// ```
716#[async_trait]
717pub trait CancellationHandler: Send + Sync + std::fmt::Debug {
718    /// Handle a cancellation notification
719    ///
720    /// This method is called when the server cancels a request that the client
721    /// previously issued.
722    ///
723    /// # Arguments
724    ///
725    /// * `notification` - The cancellation notification containing request ID and optional reason
726    ///
727    /// # Returns
728    ///
729    /// Returns `Ok(())` if the cancellation was processed successfully.
730    async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()>;
731}
732
733// ============================================================================
734// LIST CHANGED HANDLER TRAITS
735// ============================================================================
736
737/// Handler for resource list changes
738///
739/// Per MCP 2025-06-18 specification, `notifications/resources/list_changed` is
740/// an optional notification from the server to the client, informing it that the
741/// list of resources it can read from has changed.
742///
743/// This notification has no parameters - it simply signals that the client should
744/// re-query the server's resource list if needed.
745///
746/// # Examples
747///
748/// ```rust,no_run
749/// use turbomcp_client::handlers::{ResourceListChangedHandler, HandlerResult};
750/// use async_trait::async_trait;
751///
752/// #[derive(Debug)]
753/// struct MyResourceListHandler;
754///
755/// #[async_trait]
756/// impl ResourceListChangedHandler for MyResourceListHandler {
757///     async fn handle_resource_list_changed(&self) -> HandlerResult<()> {
758///         println!("Server's resource list changed - refreshing...");
759///         // In a real implementation, re-query: client.list_resources().await
760///         Ok(())
761///     }
762/// }
763/// ```
764#[async_trait]
765pub trait ResourceListChangedHandler: Send + Sync + std::fmt::Debug {
766    /// Handle a resource list changed notification
767    ///
768    /// This method is called when the server's available resource list changes.
769    ///
770    /// # Returns
771    ///
772    /// Returns `Ok(())` if the notification was processed successfully.
773    async fn handle_resource_list_changed(&self) -> HandlerResult<()>;
774}
775
776/// Handler for prompt list changes
777///
778/// Per MCP 2025-06-18 specification, `notifications/prompts/list_changed` is
779/// an optional notification from the server to the client, informing it that the
780/// list of prompts it offers has changed.
781///
782/// # Examples
783///
784/// ```rust,no_run
785/// use turbomcp_client::handlers::{PromptListChangedHandler, HandlerResult};
786/// use async_trait::async_trait;
787///
788/// #[derive(Debug)]
789/// struct MyPromptListHandler;
790///
791/// #[async_trait]
792/// impl PromptListChangedHandler for MyPromptListHandler {
793///     async fn handle_prompt_list_changed(&self) -> HandlerResult<()> {
794///         println!("Server's prompt list changed - refreshing...");
795///         Ok(())
796///     }
797/// }
798/// ```
799#[async_trait]
800pub trait PromptListChangedHandler: Send + Sync + std::fmt::Debug {
801    /// Handle a prompt list changed notification
802    ///
803    /// This method is called when the server's available prompt list changes.
804    ///
805    /// # Returns
806    ///
807    /// Returns `Ok(())` if the notification was processed successfully.
808    async fn handle_prompt_list_changed(&self) -> HandlerResult<()>;
809}
810
811/// Handler for tool list changes
812///
813/// Per MCP 2025-06-18 specification, `notifications/tools/list_changed` is
814/// an optional notification from the server to the client, informing it that the
815/// list of tools it offers has changed.
816///
817/// # Examples
818///
819/// ```rust,no_run
820/// use turbomcp_client::handlers::{ToolListChangedHandler, HandlerResult};
821/// use async_trait::async_trait;
822///
823/// #[derive(Debug)]
824/// struct MyToolListHandler;
825///
826/// #[async_trait]
827/// impl ToolListChangedHandler for MyToolListHandler {
828///     async fn handle_tool_list_changed(&self) -> HandlerResult<()> {
829///         println!("Server's tool list changed - refreshing...");
830///         Ok(())
831///     }
832/// }
833/// ```
834#[async_trait]
835pub trait ToolListChangedHandler: Send + Sync + std::fmt::Debug {
836    /// Handle a tool list changed notification
837    ///
838    /// This method is called when the server's available tool list changes.
839    ///
840    /// # Returns
841    ///
842    /// Returns `Ok(())` if the notification was processed successfully.
843    async fn handle_tool_list_changed(&self) -> HandlerResult<()>;
844}
845
846// ============================================================================
847// HANDLER REGISTRY FOR CLIENT
848// ============================================================================
849
850/// Registry for managing client-side handlers
851///
852/// This registry holds all the handler implementations and provides methods
853/// for registering and invoking them. It's used internally by the Client
854/// to dispatch server-initiated requests to the appropriate handlers.
855#[derive(Debug, Default)]
856pub struct HandlerRegistry {
857    /// Roots handler for filesystem root requests
858    pub roots: Option<Arc<dyn RootsHandler>>,
859
860    /// Elicitation handler for user input requests
861    pub elicitation: Option<Arc<dyn ElicitationHandler>>,
862
863    /// Log handler for server log messages
864    pub log: Option<Arc<dyn LogHandler>>,
865
866    /// Resource update handler for resource change notifications
867    pub resource_update: Option<Arc<dyn ResourceUpdateHandler>>,
868
869    /// Cancellation handler for cancellation notifications
870    pub cancellation: Option<Arc<dyn CancellationHandler>>,
871
872    /// Resource list changed handler
873    pub resource_list_changed: Option<Arc<dyn ResourceListChangedHandler>>,
874
875    /// Prompt list changed handler
876    pub prompt_list_changed: Option<Arc<dyn PromptListChangedHandler>>,
877
878    /// Tool list changed handler
879    pub tool_list_changed: Option<Arc<dyn ToolListChangedHandler>>,
880}
881
882impl HandlerRegistry {
883    /// Create a new empty handler registry
884    #[must_use]
885    pub fn new() -> Self {
886        Self::default()
887    }
888
889    /// Register a roots handler
890    pub fn set_roots_handler(&mut self, handler: Arc<dyn RootsHandler>) {
891        debug!("Registering roots handler");
892        self.roots = Some(handler);
893    }
894
895    /// Register an elicitation handler
896    pub fn set_elicitation_handler(&mut self, handler: Arc<dyn ElicitationHandler>) {
897        debug!("Registering elicitation handler");
898        self.elicitation = Some(handler);
899    }
900
901    /// Register a log handler
902    pub fn set_log_handler(&mut self, handler: Arc<dyn LogHandler>) {
903        debug!("Registering log handler");
904        self.log = Some(handler);
905    }
906
907    /// Register a resource update handler
908    pub fn set_resource_update_handler(&mut self, handler: Arc<dyn ResourceUpdateHandler>) {
909        debug!("Registering resource update handler");
910        self.resource_update = Some(handler);
911    }
912
913    /// Register a cancellation handler
914    pub fn set_cancellation_handler(&mut self, handler: Arc<dyn CancellationHandler>) {
915        debug!("Registering cancellation handler");
916        self.cancellation = Some(handler);
917    }
918
919    /// Register a resource list changed handler
920    pub fn set_resource_list_changed_handler(
921        &mut self,
922        handler: Arc<dyn ResourceListChangedHandler>,
923    ) {
924        debug!("Registering resource list changed handler");
925        self.resource_list_changed = Some(handler);
926    }
927
928    /// Register a prompt list changed handler
929    pub fn set_prompt_list_changed_handler(&mut self, handler: Arc<dyn PromptListChangedHandler>) {
930        debug!("Registering prompt list changed handler");
931        self.prompt_list_changed = Some(handler);
932    }
933
934    /// Register a tool list changed handler
935    pub fn set_tool_list_changed_handler(&mut self, handler: Arc<dyn ToolListChangedHandler>) {
936        debug!("Registering tool list changed handler");
937        self.tool_list_changed = Some(handler);
938    }
939
940    /// Check if a roots handler is registered
941    #[must_use]
942    pub fn has_roots_handler(&self) -> bool {
943        self.roots.is_some()
944    }
945
946    /// Check if an elicitation handler is registered
947    #[must_use]
948    pub fn has_elicitation_handler(&self) -> bool {
949        self.elicitation.is_some()
950    }
951
952    /// Check if a log handler is registered
953    #[must_use]
954    pub fn has_log_handler(&self) -> bool {
955        self.log.is_some()
956    }
957
958    /// Check if a resource update handler is registered
959    #[must_use]
960    pub fn has_resource_update_handler(&self) -> bool {
961        self.resource_update.is_some()
962    }
963
964    /// Get the log handler if registered
965    #[must_use]
966    pub fn get_log_handler(&self) -> Option<Arc<dyn LogHandler>> {
967        self.log.clone()
968    }
969
970    /// Get the resource update handler if registered
971    #[must_use]
972    pub fn get_resource_update_handler(&self) -> Option<Arc<dyn ResourceUpdateHandler>> {
973        self.resource_update.clone()
974    }
975
976    /// Get the cancellation handler if registered
977    #[must_use]
978    pub fn get_cancellation_handler(&self) -> Option<Arc<dyn CancellationHandler>> {
979        self.cancellation.clone()
980    }
981
982    /// Get the resource list changed handler if registered
983    #[must_use]
984    pub fn get_resource_list_changed_handler(&self) -> Option<Arc<dyn ResourceListChangedHandler>> {
985        self.resource_list_changed.clone()
986    }
987
988    /// Get the prompt list changed handler if registered
989    #[must_use]
990    pub fn get_prompt_list_changed_handler(&self) -> Option<Arc<dyn PromptListChangedHandler>> {
991        self.prompt_list_changed.clone()
992    }
993
994    /// Get the tool list changed handler if registered
995    #[must_use]
996    pub fn get_tool_list_changed_handler(&self) -> Option<Arc<dyn ToolListChangedHandler>> {
997        self.tool_list_changed.clone()
998    }
999
1000    /// Handle a roots/list request from the server
1001    pub async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>> {
1002        match &self.roots {
1003            Some(handler) => {
1004                info!("Processing roots/list request from server");
1005                handler.handle_roots_request().await
1006            }
1007            None => {
1008                warn!("No roots handler registered, returning empty roots list");
1009                // Return empty list per MCP spec - client has no roots available
1010                Ok(Vec::new())
1011            }
1012        }
1013    }
1014
1015    /// Handle an elicitation request
1016    pub async fn handle_elicitation(
1017        &self,
1018        request: ElicitationRequest,
1019    ) -> HandlerResult<ElicitationResponse> {
1020        match &self.elicitation {
1021            Some(handler) => {
1022                info!("Processing elicitation request: {}", request.id);
1023                handler.handle_elicitation(request).await
1024            }
1025            None => {
1026                warn!("No elicitation handler registered, declining request");
1027                Err(HandlerError::Configuration {
1028                    message: "No elicitation handler registered".to_string(),
1029                })
1030            }
1031        }
1032    }
1033
1034    /// Handle a log message
1035    pub async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
1036        match &self.log {
1037            Some(handler) => handler.handle_log(log).await,
1038            None => {
1039                debug!("No log handler registered, ignoring log message");
1040                Ok(())
1041            }
1042        }
1043    }
1044
1045    /// Handle a resource update notification
1046    pub async fn handle_resource_update(
1047        &self,
1048        notification: ResourceUpdatedNotification,
1049    ) -> HandlerResult<()> {
1050        match &self.resource_update {
1051            Some(handler) => {
1052                debug!("Processing resource update: {}", notification.uri);
1053                handler.handle_resource_update(notification).await
1054            }
1055            None => {
1056                debug!("No resource update handler registered, ignoring notification");
1057                Ok(())
1058            }
1059        }
1060    }
1061}
1062
1063// ============================================================================
1064// DEFAULT HANDLER IMPLEMENTATIONS
1065// ============================================================================
1066
1067/// Default elicitation handler that declines all requests
1068#[derive(Debug)]
1069pub struct DeclineElicitationHandler;
1070
1071#[async_trait]
1072impl ElicitationHandler for DeclineElicitationHandler {
1073    async fn handle_elicitation(
1074        &self,
1075        request: ElicitationRequest,
1076    ) -> HandlerResult<ElicitationResponse> {
1077        warn!("Declining elicitation request: {}", request.message());
1078        Ok(ElicitationResponse::decline())
1079    }
1080}
1081
1082/// Default log handler that routes server logs to tracing
1083#[derive(Debug)]
1084pub struct TracingLogHandler;
1085
1086#[async_trait]
1087impl LogHandler for TracingLogHandler {
1088    async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
1089        let logger_prefix = log.logger.as_deref().unwrap_or("server");
1090
1091        // Per MCP spec: data can be any JSON type (string, object, etc.)
1092        let message = log.data.to_string();
1093        match log.level {
1094            LogLevel::Error => error!("[{}] {}", logger_prefix, message),
1095            LogLevel::Warning => warn!("[{}] {}", logger_prefix, message),
1096            LogLevel::Info => info!("[{}] {}", logger_prefix, message),
1097            LogLevel::Debug => debug!("[{}] {}", logger_prefix, message),
1098            LogLevel::Notice => info!("[{}] [NOTICE] {}", logger_prefix, message),
1099            LogLevel::Critical => error!("[{}] [CRITICAL] {}", logger_prefix, message),
1100            LogLevel::Alert => error!("[{}] [ALERT] {}", logger_prefix, message),
1101            LogLevel::Emergency => error!("[{}] [EMERGENCY] {}", logger_prefix, message),
1102        }
1103
1104        Ok(())
1105    }
1106}
1107
1108/// Default resource update handler that logs changes
1109#[derive(Debug)]
1110pub struct LoggingResourceUpdateHandler;
1111
1112#[async_trait]
1113impl ResourceUpdateHandler for LoggingResourceUpdateHandler {
1114    async fn handle_resource_update(
1115        &self,
1116        notification: ResourceUpdatedNotification,
1117    ) -> HandlerResult<()> {
1118        // Per MCP spec: notification only contains URI
1119        info!("Resource {} was updated", notification.uri);
1120        Ok(())
1121    }
1122}
1123
1124/// Default cancellation handler that logs cancellation notifications
1125#[derive(Debug)]
1126pub struct LoggingCancellationHandler;
1127
1128#[async_trait]
1129impl CancellationHandler for LoggingCancellationHandler {
1130    async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()> {
1131        if let Some(reason) = &notification.reason {
1132            info!(
1133                "Request {} was cancelled: {}",
1134                notification.request_id, reason
1135            );
1136        } else {
1137            info!("Request {} was cancelled", notification.request_id);
1138        }
1139        Ok(())
1140    }
1141}
1142
1143/// Default resource list changed handler that logs changes
1144#[derive(Debug)]
1145pub struct LoggingResourceListChangedHandler;
1146
1147#[async_trait]
1148impl ResourceListChangedHandler for LoggingResourceListChangedHandler {
1149    async fn handle_resource_list_changed(&self) -> HandlerResult<()> {
1150        info!("Server's resource list changed");
1151        Ok(())
1152    }
1153}
1154
1155/// Default prompt list changed handler that logs changes
1156#[derive(Debug)]
1157pub struct LoggingPromptListChangedHandler;
1158
1159#[async_trait]
1160impl PromptListChangedHandler for LoggingPromptListChangedHandler {
1161    async fn handle_prompt_list_changed(&self) -> HandlerResult<()> {
1162        info!("Server's prompt list changed");
1163        Ok(())
1164    }
1165}
1166
1167/// Default tool list changed handler that logs changes
1168#[derive(Debug)]
1169pub struct LoggingToolListChangedHandler;
1170
1171#[async_trait]
1172impl ToolListChangedHandler for LoggingToolListChangedHandler {
1173    async fn handle_tool_list_changed(&self) -> HandlerResult<()> {
1174        info!("Server's tool list changed");
1175        Ok(())
1176    }
1177}
1178
1179#[cfg(test)]
1180mod tests {
1181    use super::*;
1182    use serde_json::json;
1183    use tokio;
1184
1185    // Test handler implementations
1186    #[derive(Debug)]
1187    struct TestElicitationHandler;
1188
1189    #[async_trait]
1190    impl ElicitationHandler for TestElicitationHandler {
1191        async fn handle_elicitation(
1192            &self,
1193            _request: ElicitationRequest,
1194        ) -> HandlerResult<ElicitationResponse> {
1195            let mut content = HashMap::new();
1196            content.insert("test".to_string(), json!("response"));
1197            Ok(ElicitationResponse::accept(content))
1198        }
1199    }
1200
1201    #[tokio::test]
1202    async fn test_handler_registry_creation() {
1203        let registry = HandlerRegistry::new();
1204        assert!(!registry.has_elicitation_handler());
1205        assert!(!registry.has_log_handler());
1206        assert!(!registry.has_resource_update_handler());
1207    }
1208
1209    #[tokio::test]
1210    async fn test_elicitation_handler_registration() {
1211        let mut registry = HandlerRegistry::new();
1212        let handler = Arc::new(TestElicitationHandler);
1213
1214        registry.set_elicitation_handler(handler);
1215        assert!(registry.has_elicitation_handler());
1216    }
1217
1218    #[tokio::test]
1219    async fn test_elicitation_request_handling() {
1220        let mut registry = HandlerRegistry::new();
1221        let handler = Arc::new(TestElicitationHandler);
1222        registry.set_elicitation_handler(handler);
1223
1224        // Create protocol request
1225        let proto_request = turbomcp_protocol::types::ElicitRequest {
1226            params: turbomcp_protocol::types::ElicitRequestParams::form(
1227                "Test prompt".to_string(),
1228                turbomcp_protocol::types::ElicitationSchema::new(),
1229                None,
1230                None,
1231            ),
1232            task: None,
1233            _meta: None,
1234        };
1235
1236        // Wrap for handler
1237        let request = ElicitationRequest::new(
1238            turbomcp_protocol::MessageId::String("test-123".to_string()),
1239            proto_request,
1240        );
1241
1242        let response = registry.handle_elicitation(request).await.unwrap();
1243        assert_eq!(response.action(), ElicitationAction::Accept);
1244        assert!(response.content().is_some());
1245    }
1246
1247    #[tokio::test]
1248    async fn test_default_handlers() {
1249        let decline_handler = DeclineElicitationHandler;
1250
1251        // Create protocol request
1252        let proto_request = turbomcp_protocol::types::ElicitRequest {
1253            params: turbomcp_protocol::types::ElicitRequestParams::form(
1254                "Test".to_string(),
1255                turbomcp_protocol::types::ElicitationSchema::new(),
1256                None,
1257                None,
1258            ),
1259            task: None,
1260            _meta: None,
1261        };
1262
1263        // Wrap for handler
1264        let request = ElicitationRequest::new(
1265            turbomcp_protocol::MessageId::String("test".to_string()),
1266            proto_request,
1267        );
1268
1269        let response = decline_handler.handle_elicitation(request).await.unwrap();
1270        assert_eq!(response.action(), ElicitationAction::Decline);
1271    }
1272
1273    #[tokio::test]
1274    async fn test_handler_error_types() {
1275        let error = HandlerError::UserCancelled;
1276        assert!(error.to_string().contains("User cancelled"));
1277
1278        let timeout_error = HandlerError::Timeout {
1279            timeout_seconds: 30,
1280        };
1281        assert!(timeout_error.to_string().contains("30 seconds"));
1282    }
1283
1284    // ========================================================================
1285    // JSON-RPC Error Mapping Tests
1286    // ========================================================================
1287
1288    #[test]
1289    fn test_user_cancelled_error_mapping() {
1290        let error = HandlerError::UserCancelled;
1291        let jsonrpc_error = error.into_jsonrpc_error();
1292
1293        assert_eq!(
1294            jsonrpc_error.code, -1,
1295            "User cancelled should map to -1 per MCP 2025-06-18 spec"
1296        );
1297        assert!(jsonrpc_error.message.contains("User rejected"));
1298        assert!(jsonrpc_error.data.is_none());
1299    }
1300
1301    #[test]
1302    fn test_timeout_error_mapping() {
1303        let error = HandlerError::Timeout {
1304            timeout_seconds: 30,
1305        };
1306        let jsonrpc_error = error.into_jsonrpc_error();
1307
1308        assert_eq!(jsonrpc_error.code, -32801, "Timeout should map to -32801");
1309        assert!(jsonrpc_error.message.contains("30 seconds"));
1310        assert!(jsonrpc_error.data.is_none());
1311    }
1312
1313    #[test]
1314    fn test_invalid_input_error_mapping() {
1315        let error = HandlerError::InvalidInput {
1316            details: "Missing required field".to_string(),
1317        };
1318        let jsonrpc_error = error.into_jsonrpc_error();
1319
1320        assert_eq!(
1321            jsonrpc_error.code, -32602,
1322            "Invalid input should map to -32602"
1323        );
1324        assert!(jsonrpc_error.message.contains("Invalid input"));
1325        assert!(jsonrpc_error.message.contains("Missing required field"));
1326        assert!(jsonrpc_error.data.is_none());
1327    }
1328
1329    #[test]
1330    fn test_configuration_error_mapping() {
1331        let error = HandlerError::Configuration {
1332            message: "Handler not registered".to_string(),
1333        };
1334        let jsonrpc_error = error.into_jsonrpc_error();
1335
1336        assert_eq!(
1337            jsonrpc_error.code, -32601,
1338            "Configuration error should map to -32601"
1339        );
1340        assert!(
1341            jsonrpc_error
1342                .message
1343                .contains("Handler configuration error")
1344        );
1345        assert!(jsonrpc_error.message.contains("Handler not registered"));
1346        assert!(jsonrpc_error.data.is_none());
1347    }
1348
1349    #[test]
1350    fn test_generic_error_mapping() {
1351        let error = HandlerError::Generic {
1352            message: "Something went wrong".to_string(),
1353        };
1354        let jsonrpc_error = error.into_jsonrpc_error();
1355
1356        assert_eq!(
1357            jsonrpc_error.code, -32603,
1358            "Generic error should map to -32603"
1359        );
1360        assert!(jsonrpc_error.message.contains("Handler error"));
1361        assert!(jsonrpc_error.message.contains("Something went wrong"));
1362        assert!(jsonrpc_error.data.is_none());
1363    }
1364
1365    #[test]
1366    fn test_external_error_mapping() {
1367        let external_err = Box::new(std::io::Error::other("Database connection failed"));
1368        let error = HandlerError::External {
1369            source: external_err,
1370        };
1371        let jsonrpc_error = error.into_jsonrpc_error();
1372
1373        assert_eq!(
1374            jsonrpc_error.code, -32603,
1375            "External error should map to -32603"
1376        );
1377        assert!(jsonrpc_error.message.contains("External system error"));
1378        assert!(jsonrpc_error.message.contains("Database connection failed"));
1379        assert!(jsonrpc_error.data.is_none());
1380    }
1381
1382    #[test]
1383    fn test_error_code_uniqueness() {
1384        // Verify that user-facing errors have unique codes
1385        let user_cancelled = HandlerError::UserCancelled.into_jsonrpc_error().code;
1386        let timeout = HandlerError::Timeout { timeout_seconds: 1 }
1387            .into_jsonrpc_error()
1388            .code;
1389        let invalid_input = HandlerError::InvalidInput {
1390            details: "test".to_string(),
1391        }
1392        .into_jsonrpc_error()
1393        .code;
1394        let configuration = HandlerError::Configuration {
1395            message: "test".to_string(),
1396        }
1397        .into_jsonrpc_error()
1398        .code;
1399
1400        // These should all be different
1401        assert_ne!(user_cancelled, timeout);
1402        assert_ne!(user_cancelled, invalid_input);
1403        assert_ne!(user_cancelled, configuration);
1404        assert_ne!(timeout, invalid_input);
1405        assert_ne!(timeout, configuration);
1406        assert_ne!(invalid_input, configuration);
1407    }
1408
1409    #[test]
1410    fn test_error_messages_are_informative() {
1411        // Verify all error messages contain useful information
1412        let errors = vec![
1413            HandlerError::UserCancelled,
1414            HandlerError::Timeout {
1415                timeout_seconds: 42,
1416            },
1417            HandlerError::InvalidInput {
1418                details: "test detail".to_string(),
1419            },
1420            HandlerError::Configuration {
1421                message: "test config".to_string(),
1422            },
1423            HandlerError::Generic {
1424                message: "test generic".to_string(),
1425            },
1426        ];
1427
1428        for error in errors {
1429            let jsonrpc_error = error.into_jsonrpc_error();
1430            assert!(
1431                !jsonrpc_error.message.is_empty(),
1432                "Error message should not be empty"
1433            );
1434            assert!(
1435                jsonrpc_error.message.len() > 10,
1436                "Error message should be descriptive"
1437            );
1438        }
1439    }
1440}