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