Skip to main content

turbomcp_protocol/
handlers.rs

1//! Handler traits for extensible MCP protocol support
2//!
3//! This module provides trait definitions for handling various MCP protocol
4//! features including elicitation, completion, resource templates, and ping.
5//!
6//! ## Handler Types
7//!
8//! ### [`ElicitationHandler`]
9//! Handle server-initiated user input requests. Useful for asking users for
10//! additional information during tool execution.
11//!
12//! ### [`CompletionProvider`]
13//! Provide argument completion suggestions for tools and commands. Implements
14//! autocomplete functionality in MCP clients.
15//!
16//! ### [`ResourceTemplateHandler`]
17//! Manage dynamic resource templates with parameter substitution. Enables
18//! pattern-based resource access (e.g., `file:///{path}`).
19//!
20//! ### [`PingHandler`]
21//! Handle bidirectional ping/pong for connection health monitoring.
22//!
23//! ## Example: Implementing an Elicitation Handler
24//!
25//! ```rust
26//! use turbomcp_protocol::{ElicitationHandler, ElicitationContext, ElicitationResponse};
27//! use turbomcp_protocol::Result;
28//! use std::collections::HashMap;
29//! use std::future::Future;
30//! use std::pin::Pin;
31//!
32//! struct MyElicitationHandler;
33//!
34//! impl ElicitationHandler for MyElicitationHandler {
35//!     fn handle_elicitation(
36//!         &self,
37//!         context: &ElicitationContext
38//!     ) -> Pin<Box<dyn Future<Output = Result<ElicitationResponse>> + Send + '_>> {
39//!         let can_handle = self.can_handle(context);
40//!         Box::pin(async move {
41//!             // Check if we can handle this elicitation type
42//!             if !can_handle {
43//!                 return Ok(ElicitationResponse {
44//!                     accepted: false,
45//!                     content: None,
46//!                     decline_reason: Some("Unsupported elicitation type".to_string()),
47//!                 });
48//!             }
49//!
50//!             // Process the elicitation (e.g., prompt user)
51//!             let mut response_data = HashMap::new();
52//!             response_data.insert(
53//!                 "user_input".to_string(),
54//!                 serde_json::json!("User provided value")
55//!             );
56//!
57//!             Ok(ElicitationResponse {
58//!                 accepted: true,
59//!                 content: Some(response_data),
60//!                 decline_reason: None,
61//!             })
62//!         })
63//!     }
64//!
65//!     fn can_handle(&self, context: &ElicitationContext) -> bool {
66//!         // Check if elicitation has required input
67//!         context.required && !context.message.is_empty()
68//!     }
69//!
70//!     fn priority(&self) -> i32 {
71//!         100 // Higher priority than default (0)
72//!     }
73//! }
74//! ```
75//!
76//! ## Example: Implementing a Completion Provider
77//!
78//! ```rust
79//! use turbomcp_protocol::{CompletionProvider, CompletionContext, CompletionItem};
80//! use turbomcp_protocol::Result;
81//! use std::future::Future;
82//! use std::pin::Pin;
83//!
84//! struct FilePathCompletionProvider;
85//!
86//! impl CompletionProvider for FilePathCompletionProvider {
87//!     fn provide_completions(
88//!         &self,
89//!         context: &CompletionContext
90//!     ) -> Pin<Box<dyn Future<Output = Result<Vec<CompletionItem>>> + Send + '_>> {
91//!         Box::pin(async move {
92//!             // Provide file path completions
93//!             let completions = vec![
94//!                 CompletionItem {
95//!                     value: "/home/user/documents".to_string(),
96//!                     label: Some("Documents".to_string()),
97//!                     documentation: Some("User documents folder".to_string()),
98//!                     sort_priority: Some(1),
99//!                     insert_text: None,
100//!                     metadata: Default::default(),
101//!                 },
102//!                 CompletionItem {
103//!                     value: "/home/user/downloads".to_string(),
104//!                     label: Some("Downloads".to_string()),
105//!                     documentation: Some("Downloads folder".to_string()),
106//!                     sort_priority: Some(2),
107//!                     insert_text: None,
108//!                     metadata: Default::default(),
109//!                 },
110//!             ];
111//!
112//!             Ok(completions)
113//!         })
114//!     }
115//!
116//!     fn can_provide(&self, context: &CompletionContext) -> bool {
117//!         // Only provide completions for "path" arguments
118//!         context.argument_name.as_deref() == Some("path")
119//!     }
120//! }
121//! ```
122
123use serde_json::Value;
124use std::collections::HashMap;
125
126use std::future::Future;
127use std::pin::Pin;
128
129use crate::Result;
130use crate::context::{CompletionContext, ElicitationContext, ServerInitiatedContext};
131
132/// Handler for server-initiated elicitation requests
133pub trait ElicitationHandler: Send + Sync {
134    /// Handle an elicitation request from the server
135    fn handle_elicitation(
136        &self,
137        context: &ElicitationContext,
138    ) -> Pin<Box<dyn Future<Output = Result<ElicitationResponse>> + Send + '_>>;
139
140    /// Check if this handler can process the given elicitation
141    fn can_handle(&self, context: &ElicitationContext) -> bool;
142
143    /// Get handler priority (higher = higher priority)
144    fn priority(&self) -> i32 {
145        0
146    }
147}
148
149/// Response to an elicitation request
150#[derive(Debug, Clone)]
151pub struct ElicitationResponse {
152    /// Whether the elicitation was accepted
153    pub accepted: bool,
154    /// The response content if accepted
155    pub content: Option<HashMap<String, Value>>,
156    /// Optional reason for declining
157    pub decline_reason: Option<String>,
158}
159
160/// Provider for argument completion
161pub trait CompletionProvider: Send + Sync {
162    /// Provide completions for the given context
163    fn provide_completions(
164        &self,
165        context: &CompletionContext,
166    ) -> Pin<Box<dyn Future<Output = Result<Vec<CompletionItem>>> + Send + '_>>;
167
168    /// Check if this provider can handle the completion request
169    fn can_provide(&self, context: &CompletionContext) -> bool;
170
171    /// Get provider priority
172    fn priority(&self) -> i32 {
173        0
174    }
175}
176
177/// A single completion item
178#[derive(Debug, Clone)]
179pub struct CompletionItem {
180    /// The completion value
181    pub value: String,
182    /// Human-readable label
183    pub label: Option<String>,
184    /// Additional documentation
185    pub documentation: Option<String>,
186    /// Sort priority (lower = higher priority)
187    pub sort_priority: Option<i32>,
188    /// Text to insert
189    pub insert_text: Option<String>,
190    /// Item metadata
191    pub metadata: HashMap<String, Value>,
192}
193
194/// Handler for resource templates
195pub trait ResourceTemplateHandler: Send + Sync {
196    /// List available resource templates
197    fn list_templates(
198        &self,
199    ) -> Pin<Box<dyn Future<Output = Result<Vec<ResourceTemplate>>> + Send + '_>>;
200
201    /// Get a specific resource template
202    fn get_template(
203        &self,
204        name: &str,
205    ) -> Pin<Box<dyn Future<Output = Result<Option<ResourceTemplate>>> + Send + '_>>;
206
207    /// Resolve template parameters
208    fn resolve_template(
209        &self,
210        template: &ResourceTemplate,
211        params: HashMap<String, Value>,
212    ) -> Pin<Box<dyn Future<Output = Result<ResolvedResource>> + Send + '_>>;
213}
214
215/// Resource template definition
216#[derive(Debug, Clone)]
217pub struct ResourceTemplate {
218    /// Template name
219    pub name: String,
220    /// Template description
221    pub description: Option<String>,
222    /// URI template pattern
223    pub uri_template: String,
224    /// Template parameters
225    pub parameters: Vec<TemplateParam>,
226    /// Template metadata
227    pub metadata: HashMap<String, Value>,
228}
229
230/// Template parameter definition
231#[derive(Debug, Clone)]
232pub struct TemplateParam {
233    /// Parameter name
234    pub name: String,
235    /// Parameter description
236    pub description: Option<String>,
237    /// Whether the parameter is required
238    pub required: bool,
239    /// Parameter type
240    pub param_type: String,
241    /// Default value
242    pub default_value: Option<Value>,
243}
244
245/// Resolved resource from template
246#[derive(Debug, Clone)]
247pub struct ResolvedResource {
248    /// Resolved URI
249    pub uri: String,
250    /// Resource name
251    pub name: String,
252    /// Resource description
253    pub description: Option<String>,
254    /// Resource content
255    pub content: Option<Value>,
256    /// Resource metadata
257    pub metadata: HashMap<String, Value>,
258}
259
260/// Handler for bidirectional ping requests
261pub trait PingHandler: Send + Sync {
262    /// Handle a ping request
263    fn handle_ping(
264        &self,
265        context: &ServerInitiatedContext,
266    ) -> Pin<Box<dyn Future<Output = Result<PingResponse>> + Send + '_>>;
267
268    /// Send a ping to the remote party
269    fn send_ping(
270        &self,
271        target: &str,
272    ) -> Pin<Box<dyn Future<Output = Result<PingResponse>> + Send + '_>>;
273}
274
275/// Response to a ping request
276#[derive(Debug, Clone)]
277pub struct PingResponse {
278    /// Whether the ping was successful
279    pub success: bool,
280    /// Round-trip time in milliseconds
281    pub rtt_ms: Option<u64>,
282    /// Additional metadata
283    pub metadata: HashMap<String, Value>,
284}
285
286/// Capabilities for server-initiated features
287#[derive(Debug, Clone, Default)]
288pub struct ServerInitiatedCapabilities {
289    /// Supports sampling/message creation
290    pub sampling: bool,
291    /// Supports roots listing
292    pub roots: bool,
293    /// Supports elicitation
294    pub elicitation: bool,
295    /// Maximum concurrent requests
296    pub max_concurrent_requests: usize,
297    /// Supported experimental features
298    pub experimental: HashMap<String, bool>,
299}
300
301/// Handler capability tracking
302#[derive(Debug, Clone, Default)]
303pub struct HandlerCapabilities {
304    /// Supports elicitation
305    pub elicitation: bool,
306    /// Supports completion
307    pub completion: bool,
308    /// Supports resource templates
309    pub templates: bool,
310    /// Supports bidirectional ping
311    pub ping: bool,
312    /// Server-initiated capabilities
313    pub server_initiated: ServerInitiatedCapabilities,
314}
315
316impl HandlerCapabilities {
317    /// Create new handler capabilities
318    pub fn new() -> Self {
319        Self::default()
320    }
321
322    /// Enable elicitation support
323    pub fn with_elicitation(mut self) -> Self {
324        self.elicitation = true;
325        self
326    }
327
328    /// Enable completion support
329    pub fn with_completion(mut self) -> Self {
330        self.completion = true;
331        self
332    }
333
334    /// Enable template support
335    pub fn with_templates(mut self) -> Self {
336        self.templates = true;
337        self
338    }
339
340    /// Enable ping support
341    pub fn with_ping(mut self) -> Self {
342        self.ping = true;
343        self
344    }
345
346    /// Set server-initiated capabilities
347    pub fn with_server_initiated(mut self, capabilities: ServerInitiatedCapabilities) -> Self {
348        self.server_initiated = capabilities;
349        self
350    }
351}
352
353/// Handler for JSON-RPC requests - Core abstraction for MCP protocol implementation
354///
355/// This trait provides a transport-agnostic interface for handling MCP JSON-RPC requests.
356/// Implementations of this trait can work seamlessly across all transport layers
357/// (HTTP, STDIO, WebSocket, etc.) without transport-specific code.
358///
359/// # Architecture
360///
361/// The `JsonRpcHandler` trait serves as the bridge between:
362/// - **Protocol Logic**: Tools, resources, prompts dispatch (typically macro-generated)
363/// - **Transport Layer**: HTTP, STDIO, WebSocket protocol details
364///
365/// This separation enables:
366/// - Clean, testable handler implementations
367/// - Transport-agnostic server code
368/// - Full current MCP compliance in the transport layer
369/// - Compile-time dispatch optimizations in handlers
370///
371/// # Example: Macro-Generated Implementation
372///
373/// ```rust,ignore
374/// use turbomcp_protocol::JsonRpcHandler;
375/// use serde_json::Value;
376///
377/// #[derive(Clone)]
378/// struct WeatherServer;
379///
380/// impl JsonRpcHandler for WeatherServer {
381///     async fn handle_request(&self, req: Value) -> Value {
382///         // Parse method and dispatch
383///         let method = req["method"].as_str().unwrap_or("");
384///         match method {
385///             "initialize" => { /* ... */ },
386///             "tools/call" => { /* dispatch to tools */ },
387///             "resources/read" => { /* dispatch to resources */ },
388///             _ => serde_json::json!({"error": "method not found"}),
389///         }
390///     }
391///
392///     fn server_info(&self) -> ServerInfo {
393///         ServerInfo {
394///             name: "Weather Server".to_string(),
395///             version: "1.0.0".to_string(),
396///         }
397///     }
398/// }
399/// ```
400///
401/// # Usage with Transports
402///
403/// ```rust,ignore
404/// // HTTP Transport
405/// use turbomcp_transport::streamable_http::StreamableHttpTransport;
406///
407/// let handler = Arc::new(WeatherServer);
408/// let transport = StreamableHttpTransport::new(config, handler);
409/// transport.run().await?;
410///
411/// // STDIO Transport
412/// use turbomcp_transport::stdio::StdioTransport;
413///
414/// let handler = Arc::new(WeatherServer);
415/// let transport = StdioTransport::new(handler);
416/// transport.run().await?;
417/// ```
418pub trait JsonRpcHandler: Send + Sync + 'static {
419    /// Handle a JSON-RPC request and return a response
420    ///
421    /// This method receives a JSON-RPC request as a `serde_json::Value` and must return
422    /// a valid JSON-RPC response. The implementation should:
423    /// - Route the request based on the `method` field
424    /// - Validate parameters
425    /// - Execute the appropriate handler logic
426    /// - Return a success response with results or an error response
427    ///
428    /// # Arguments
429    ///
430    /// * `request` - The JSON-RPC request as a JSON value
431    ///
432    /// # Returns
433    ///
434    /// A JSON-RPC response as a JSON value containing either:
435    /// - `result`: For successful operations
436    /// - `error`: For failed operations with error details
437    ///
438    /// # Note
439    ///
440    /// The request and response are `serde_json::Value` to avoid tight coupling with
441    /// protocol types. Transport layers handle conversion to/from typed structs.
442    fn handle_request(
443        &self,
444        request: serde_json::Value,
445    ) -> Pin<Box<dyn Future<Output = serde_json::Value> + Send + '_>>;
446
447    /// Get server metadata
448    ///
449    /// Returns information about the server including name and version.
450    /// This is used during the MCP initialization handshake.
451    ///
452    /// # Returns
453    ///
454    /// Server information including name and version
455    fn server_info(&self) -> ServerInfo {
456        ServerInfo {
457            name: "TurboMCP Server".to_string(),
458            version: env!("CARGO_PKG_VERSION").to_string(),
459        }
460    }
461
462    /// Get server capabilities
463    ///
464    /// Returns the capabilities supported by this server.
465    /// Override this to advertise custom capabilities to clients.
466    ///
467    /// # Returns
468    ///
469    /// JSON value describing server capabilities
470    fn capabilities(&self) -> serde_json::Value {
471        serde_json::json!({})
472    }
473}
474
475/// Server metadata information
476#[derive(Debug, Clone)]
477pub struct ServerInfo {
478    /// Server name
479    pub name: String,
480    /// Server version
481    pub version: String,
482}