Skip to main content

turbomcp_client/client/operations/
handlers.rs

1//! Handler registration operations for MCP client
2//!
3//! This module provides methods for registering and managing various event handlers
4//! that process server-initiated operations and notifications.
5
6use crate::handlers::{
7    CancellationHandler, ElicitationHandler, LogHandler, ProgressHandler, PromptListChangedHandler,
8    ResourceListChangedHandler, ResourceUpdateHandler, RootsHandler, ToolListChangedHandler,
9};
10use std::sync::Arc;
11
12impl<T: turbomcp_transport::Transport + 'static> super::super::core::Client<T> {
13    /// Register a roots handler for responding to server filesystem root requests
14    ///
15    /// Roots handlers respond to `roots/list` requests from servers (SERVER->CLIENT).
16    /// Per the current MCP specification, servers ask clients what filesystem roots
17    /// they have access to. This is commonly used when servers need to understand
18    /// their operating boundaries, such as which repositories or project directories
19    /// they can access.
20    ///
21    /// # Arguments
22    ///
23    /// * `handler` - The roots handler implementation
24    ///
25    /// # Examples
26    ///
27    /// ```rust,no_run
28    /// use turbomcp_client::Client;
29    /// use turbomcp_client::handlers::{RootsHandler, HandlerResult};
30    /// use turbomcp_protocol::types::Root;
31    /// use turbomcp_transport::stdio::StdioTransport;
32    /// use std::sync::Arc;
33    /// use std::future::Future;
34    /// use std::pin::Pin;
35    ///
36    /// #[derive(Debug)]
37    /// struct MyRootsHandler {
38    ///     project_dir: String,
39    /// }
40    ///
41    /// impl RootsHandler for MyRootsHandler {
42    ///     fn handle_roots_request(&self) -> Pin<Box<dyn Future<Output = HandlerResult<Vec<Root>>> + Send + '_>> {
43    ///         Box::pin(async move {
44    ///             Ok(vec![Root {
45    ///                 uri: format!("file://{}", self.project_dir).into(),
46    ///                 name: Some("My Project".to_string()),
47    ///                 _meta: None,
48    ///             }])
49    ///         })
50    ///     }
51    /// }
52    ///
53    /// let mut client = Client::new(StdioTransport::new());
54    /// client.set_roots_handler(Arc::new(MyRootsHandler {
55    ///     project_dir: "/home/user/projects/myproject".to_string(),
56    /// }));
57    /// ```
58    pub fn set_roots_handler(&self, handler: Arc<dyn RootsHandler>) {
59        self.inner.handlers.lock().set_roots_handler(handler);
60    }
61
62    /// Register an elicitation handler for processing user input requests
63    ///
64    /// Elicitation handlers are called when the server needs user input during
65    /// operations. The handler should present the request to the user and
66    /// collect their response according to the provided schema.
67    ///
68    /// # Arguments
69    ///
70    /// * `handler` - The elicitation handler implementation
71    ///
72    /// # Examples
73    ///
74    /// ```rust,no_run
75    /// use turbomcp_client::Client;
76    /// use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerResult};
77    /// use turbomcp_transport::stdio::StdioTransport;
78    /// use std::sync::Arc;
79    /// use serde_json::json;
80    /// use std::future::Future;
81    /// use std::pin::Pin;
82    ///
83    /// #[derive(Debug)]
84    /// struct MyElicitationHandler;
85    ///
86    /// impl ElicitationHandler for MyElicitationHandler {
87    ///     fn handle_elicitation(
88    ///         &self,
89    ///         request: ElicitationRequest,
90    ///     ) -> Pin<Box<dyn Future<Output = HandlerResult<ElicitationResponse>> + Send + '_>> {
91    ///         Box::pin(async move {
92    ///             let mut content = std::collections::HashMap::new();
93    ///             content.insert("user_input".to_string(), json!("example"));
94    ///             Ok(ElicitationResponse::accept(content))
95    ///         })
96    ///     }
97    /// }
98    ///
99    /// let mut client = Client::new(StdioTransport::new());
100    /// client.set_elicitation_handler(Arc::new(MyElicitationHandler));
101    /// ```
102    pub fn set_elicitation_handler(&self, handler: Arc<dyn ElicitationHandler>) {
103        self.inner.handlers.lock().set_elicitation_handler(handler);
104    }
105
106    /// Register a log handler for processing server log messages
107    ///
108    /// Log handlers receive log messages from the server and can route them
109    /// to the client's logging system. This is useful for debugging and
110    /// maintaining a unified log across client and server.
111    ///
112    /// # Arguments
113    ///
114    /// * `handler` - The log handler implementation
115    ///
116    /// # Examples
117    ///
118    /// ```rust,no_run
119    /// use turbomcp_client::Client;
120    /// use turbomcp_client::handlers::{LogHandler, LoggingNotification, HandlerResult};
121    /// use turbomcp_transport::stdio::StdioTransport;
122    /// use std::sync::Arc;
123    /// use std::future::Future;
124    /// use std::pin::Pin;
125    ///
126    /// #[derive(Debug)]
127    /// struct MyLogHandler;
128    ///
129    /// impl LogHandler for MyLogHandler {
130    ///     fn handle_log(&self, log: LoggingNotification) -> Pin<Box<dyn Future<Output = HandlerResult<()>> + Send + '_>> {
131    ///         Box::pin(async move {
132    ///             println!("Server log: {}", log.data);
133    ///             Ok(())
134    ///         })
135    ///     }
136    /// }
137    ///
138    /// let mut client = Client::new(StdioTransport::new());
139    /// client.set_log_handler(Arc::new(MyLogHandler));
140    /// ```
141    pub fn set_log_handler(&self, handler: Arc<dyn LogHandler>) {
142        self.inner.handlers.lock().set_log_handler(handler);
143    }
144
145    /// Register a resource update handler for processing resource change notifications
146    ///
147    /// Resource update handlers receive notifications when subscribed resources
148    /// change on the server. Supports reactive updates to cached data or
149    /// UI refreshes when server-side resources change.
150    ///
151    /// # Arguments
152    ///
153    /// * `handler` - The resource update handler implementation
154    ///
155    /// # Examples
156    ///
157    /// ```rust,no_run
158    /// use turbomcp_client::Client;
159    /// use turbomcp_client::handlers::{ResourceUpdateHandler, ResourceUpdatedNotification, HandlerResult};
160    /// use turbomcp_transport::stdio::StdioTransport;
161    /// use std::sync::Arc;
162    /// use std::future::Future;
163    /// use std::pin::Pin;
164    ///
165    /// #[derive(Debug)]
166    /// struct MyResourceUpdateHandler;
167    ///
168    /// impl ResourceUpdateHandler for MyResourceUpdateHandler {
169    ///     fn handle_resource_update(
170    ///         &self,
171    ///         notification: ResourceUpdatedNotification,
172    ///     ) -> Pin<Box<dyn Future<Output = HandlerResult<()>> + Send + '_>> {
173    ///         Box::pin(async move {
174    ///             println!("Resource updated: {}", notification.uri);
175    ///             Ok(())
176    ///         })
177    ///     }
178    /// }
179    ///
180    /// let mut client = Client::new(StdioTransport::new());
181    /// client.set_resource_update_handler(Arc::new(MyResourceUpdateHandler));
182    /// ```
183    pub fn set_resource_update_handler(&self, handler: Arc<dyn ResourceUpdateHandler>) {
184        self.inner
185            .handlers
186            .lock()
187            .set_resource_update_handler(handler);
188    }
189
190    /// Register a cancellation handler for processing cancellation notifications
191    ///
192    /// Per the current MCP specification, cancellation notifications can be sent
193    /// by the server to indicate that a previously-issued request is being cancelled.
194    ///
195    /// # Arguments
196    ///
197    /// * `handler` - The cancellation handler implementation
198    pub fn set_cancellation_handler(&self, handler: Arc<dyn CancellationHandler>) {
199        self.inner.handlers.lock().set_cancellation_handler(handler);
200    }
201
202    /// Register a resource list changed handler
203    ///
204    /// This handler is called when the server's available resource list changes.
205    ///
206    /// # Arguments
207    ///
208    /// * `handler` - The resource list changed handler implementation
209    pub fn set_resource_list_changed_handler(&self, handler: Arc<dyn ResourceListChangedHandler>) {
210        self.inner
211            .handlers
212            .lock()
213            .set_resource_list_changed_handler(handler);
214    }
215
216    /// Register a prompt list changed handler
217    ///
218    /// This handler is called when the server's available prompt list changes.
219    ///
220    /// # Arguments
221    ///
222    /// * `handler` - The prompt list changed handler implementation
223    pub fn set_prompt_list_changed_handler(&self, handler: Arc<dyn PromptListChangedHandler>) {
224        self.inner
225            .handlers
226            .lock()
227            .set_prompt_list_changed_handler(handler);
228    }
229
230    /// Register a tool list changed handler
231    ///
232    /// This handler is called when the server's available tool list changes.
233    ///
234    /// # Arguments
235    ///
236    /// * `handler` - The tool list changed handler implementation
237    pub fn set_tool_list_changed_handler(&self, handler: Arc<dyn ToolListChangedHandler>) {
238        self.inner
239            .handlers
240            .lock()
241            .set_tool_list_changed_handler(handler);
242    }
243
244    /// Check if a roots handler is registered
245    #[must_use]
246    pub fn has_roots_handler(&self) -> bool {
247        self.inner.handlers.lock().has_roots_handler()
248    }
249
250    /// Check if an elicitation handler is registered
251    #[must_use]
252    pub fn has_elicitation_handler(&self) -> bool {
253        self.inner.handlers.lock().has_elicitation_handler()
254    }
255
256    /// Check if a log handler is registered
257    #[must_use]
258    pub fn has_log_handler(&self) -> bool {
259        self.inner.handlers.lock().has_log_handler()
260    }
261
262    /// Check if a resource update handler is registered
263    #[must_use]
264    pub fn has_resource_update_handler(&self) -> bool {
265        self.inner.handlers.lock().has_resource_update_handler()
266    }
267
268    /// Register a progress handler for processing progress notifications
269    ///
270    /// Progress handlers receive progress notifications from the server for
271    /// long-running operations. The notification includes a progress token,
272    /// current progress, optional total, and optional message.
273    ///
274    /// # Arguments
275    ///
276    /// * `handler` - The progress handler implementation
277    pub fn set_progress_handler(&self, handler: Arc<dyn ProgressHandler>) {
278        self.inner.handlers.lock().set_progress_handler(handler);
279    }
280
281    /// Check if a progress handler is registered
282    #[must_use]
283    pub fn has_progress_handler(&self) -> bool {
284        self.inner.handlers.lock().has_progress_handler()
285    }
286
287    /// Check if a tool list changed handler is registered
288    #[must_use]
289    pub fn has_tool_list_changed_handler(&self) -> bool {
290        self.inner.handlers.lock().has_tool_list_changed_handler()
291    }
292
293    /// Trigger the tool list changed handler, if one is registered.
294    ///
295    /// This programmatically invokes the same handler that would be called when
296    /// the server sends a `notifications/tools/list_changed` notification.
297    /// Useful for testing and for integration scenarios where notifications
298    /// are received through an external mechanism.
299    ///
300    /// The handler will typically re-fetch the tool list from the server and
301    /// update any downstream consumers. Returns `Ok(())` if the handler
302    /// completed successfully or if no handler was registered.
303    ///
304    /// Note: the mutex on the handler registry is released before the handler
305    /// is awaited, so the handler must not re-acquire the registry lock (e.g.
306    /// by calling `set_tool_list_changed_handler`) or it will deadlock.
307    pub async fn trigger_tool_list_changed(&self) -> crate::handlers::HandlerResult<()> {
308        let handler_opt = self.inner.handlers.lock().get_tool_list_changed_handler();
309
310        if let Some(handler) = handler_opt {
311            handler.handle_tool_list_changed().await
312        } else {
313            tracing::debug!("trigger_tool_list_changed called but no handler registered");
314            Ok(())
315        }
316    }
317}