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