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}