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