turbomcp_server/
error.rs

1//! Server error types and handling
2
3/// Result type for server operations
4pub type ServerResult<T> = Result<T, ServerError>;
5
6/// Comprehensive server error types
7#[derive(Debug, thiserror::Error)]
8#[non_exhaustive]
9pub enum ServerError {
10    /// Protocol-level error from client or protocol layer
11    ///
12    /// This variant preserves the original protocol error, including error codes
13    /// like `-1` for user rejection. This ensures transparency when forwarding
14    /// client errors (e.g., sampling/elicitation rejections) back through the
15    /// server to calling clients.
16    ///
17    /// When converting to `turbomcp_protocol::Error`, this variant is unwrapped
18    /// directly to preserve error semantics and codes.
19    #[error("Protocol error: {0}")]
20    Protocol(Box<turbomcp_protocol::Error>),
21
22    /// Core errors
23    #[error("Core error: {0}")]
24    Core(#[from] turbomcp_protocol::registry::RegistryError),
25
26    /// Transport layer errors
27    #[error("Transport error: {0}")]
28    Transport(#[from] turbomcp_transport::TransportError),
29
30    /// Handler registration errors
31    #[error("Handler error: {message}")]
32    Handler {
33        /// Error message
34        message: String,
35        /// Optional error context
36        context: Option<String>,
37    },
38
39    /// Configuration errors
40    #[error("Configuration error: {message}")]
41    Configuration {
42        /// Error message
43        message: String,
44        /// Configuration key that caused the error
45        key: Option<String>,
46    },
47
48    /// Authentication errors
49    #[error("Authentication error: {message}")]
50    Authentication {
51        /// Error message
52        message: String,
53        /// Authentication method that failed
54        method: Option<String>,
55    },
56
57    /// Authorization errors
58    #[error("Authorization error: {message}")]
59    Authorization {
60        /// Error message
61        message: String,
62        /// Resource being accessed
63        resource: Option<String>,
64    },
65
66    /// Rate limiting errors
67    #[error("Rate limit exceeded: {message}")]
68    RateLimit {
69        /// Error message
70        message: String,
71        /// Retry after seconds
72        retry_after: Option<u64>,
73    },
74
75    /// Server lifecycle errors
76    #[error("Lifecycle error: {0}")]
77    Lifecycle(String),
78
79    /// Server shutdown errors
80    #[error("Shutdown error: {0}")]
81    Shutdown(String),
82
83    /// Middleware errors
84    #[error("Middleware error: {name}: {message}")]
85    Middleware {
86        /// Middleware name
87        name: String,
88        /// Error message
89        message: String,
90    },
91
92    /// Registry errors
93    #[error("Registry error: {0}")]
94    Registry(String),
95
96    /// Routing errors
97    #[error("Routing error: {message}")]
98    Routing {
99        /// Error message
100        message: String,
101        /// Request method that failed
102        method: Option<String>,
103    },
104
105    /// Resource not found
106    #[error("Resource not found: {resource}")]
107    NotFound {
108        /// Resource that was not found
109        resource: String,
110    },
111
112    /// Internal server errors
113    #[error("Internal server error: {0}")]
114    Internal(String),
115
116    /// IO errors
117    #[error("IO error: {0}")]
118    Io(#[from] std::io::Error),
119
120    /// Serialization errors
121    #[error("Serialization error: {0}")]
122    Serialization(#[from] serde_json::Error),
123
124    /// Timeout errors
125    #[error("Timeout error: {operation} timed out after {timeout_ms}ms")]
126    Timeout {
127        /// Operation that timed out
128        operation: String,
129        /// Timeout in milliseconds
130        timeout_ms: u64,
131    },
132
133    /// Resource exhaustion
134    #[error("Resource exhausted: {resource}")]
135    ResourceExhausted {
136        /// Resource type
137        resource: String,
138        /// Current usage
139        current: Option<usize>,
140        /// Maximum allowed
141        max: Option<usize>,
142    },
143}
144
145impl ServerError {
146    /// Create a new handler error
147    pub fn handler(message: impl Into<String>) -> Self {
148        Self::Handler {
149            message: message.into(),
150            context: None,
151        }
152    }
153
154    /// Create a handler error with context
155    pub fn handler_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
156        Self::Handler {
157            message: message.into(),
158            context: Some(context.into()),
159        }
160    }
161
162    /// Create a new configuration error
163    pub fn configuration(message: impl Into<String>) -> Self {
164        Self::Configuration {
165            message: message.into(),
166            key: None,
167        }
168    }
169
170    /// Create a configuration error with key
171    pub fn configuration_with_key(message: impl Into<String>, key: impl Into<String>) -> Self {
172        Self::Configuration {
173            message: message.into(),
174            key: Some(key.into()),
175        }
176    }
177
178    /// Create a new authentication error
179    pub fn authentication(message: impl Into<String>) -> Self {
180        Self::Authentication {
181            message: message.into(),
182            method: None,
183        }
184    }
185
186    /// Create an authentication error with method
187    pub fn authentication_with_method(
188        message: impl Into<String>,
189        method: impl Into<String>,
190    ) -> Self {
191        Self::Authentication {
192            message: message.into(),
193            method: Some(method.into()),
194        }
195    }
196
197    /// Create a new authorization error
198    pub fn authorization(message: impl Into<String>) -> Self {
199        Self::Authorization {
200            message: message.into(),
201            resource: None,
202        }
203    }
204
205    /// Create an authorization error with resource
206    pub fn authorization_with_resource(
207        message: impl Into<String>,
208        resource: impl Into<String>,
209    ) -> Self {
210        Self::Authorization {
211            message: message.into(),
212            resource: Some(resource.into()),
213        }
214    }
215
216    /// Create a new rate limit error
217    pub fn rate_limit(message: impl Into<String>) -> Self {
218        Self::RateLimit {
219            message: message.into(),
220            retry_after: None,
221        }
222    }
223
224    /// Create a rate limit error with retry after
225    pub fn rate_limit_with_retry(message: impl Into<String>, retry_after: u64) -> Self {
226        Self::RateLimit {
227            message: message.into(),
228            retry_after: Some(retry_after),
229        }
230    }
231
232    /// Create a new middleware error
233    pub fn middleware(name: impl Into<String>, message: impl Into<String>) -> Self {
234        Self::Middleware {
235            name: name.into(),
236            message: message.into(),
237        }
238    }
239
240    /// Create a new routing error
241    pub fn routing(message: impl Into<String>) -> Self {
242        Self::Routing {
243            message: message.into(),
244            method: None,
245        }
246    }
247
248    /// Create a routing error with method
249    pub fn routing_with_method(message: impl Into<String>, method: impl Into<String>) -> Self {
250        Self::Routing {
251            message: message.into(),
252            method: Some(method.into()),
253        }
254    }
255
256    /// Create a not found error
257    pub fn not_found(resource: impl Into<String>) -> Self {
258        Self::NotFound {
259            resource: resource.into(),
260        }
261    }
262
263    /// Create a timeout error
264    pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
265        Self::Timeout {
266            operation: operation.into(),
267            timeout_ms,
268        }
269    }
270
271    /// Create a resource exhausted error
272    pub fn resource_exhausted(resource: impl Into<String>) -> Self {
273        Self::ResourceExhausted {
274            resource: resource.into(),
275            current: None,
276            max: None,
277        }
278    }
279
280    /// Create a resource exhausted error with usage info
281    pub fn resource_exhausted_with_usage(
282        resource: impl Into<String>,
283        current: usize,
284        max: usize,
285    ) -> Self {
286        Self::ResourceExhausted {
287            resource: resource.into(),
288            current: Some(current),
289            max: Some(max),
290        }
291    }
292
293    /// Check if this error is retryable
294    #[must_use]
295    pub const fn is_retryable(&self) -> bool {
296        matches!(
297            self,
298            Self::Timeout { .. } | Self::ResourceExhausted { .. } | Self::RateLimit { .. }
299        )
300    }
301
302    /// Check if this error should cause server shutdown
303    #[must_use]
304    pub const fn is_fatal(&self) -> bool {
305        matches!(
306            self,
307            Self::Lifecycle(_) | Self::Shutdown(_) | Self::Internal(_)
308        )
309    }
310
311    /// Get error code for JSON-RPC responses
312    #[must_use]
313    pub fn error_code(&self) -> i32 {
314        let code = match self {
315            // Preserve protocol error codes directly
316            Self::Protocol(protocol_err) => {
317                let extracted_code = protocol_err.jsonrpc_error_code();
318                tracing::info!(
319                    "🔍 [ServerError::error_code] Protocol variant - extracted code: {}, kind: {:?}",
320                    extracted_code,
321                    protocol_err.kind
322                );
323                extracted_code
324            }
325
326            // Map server errors to JSON-RPC codes
327            Self::Core(_) => -32603,
328            Self::NotFound { .. } => -32004,
329            Self::Authentication { .. } => -32008,
330            Self::Authorization { .. } => -32005,
331            Self::RateLimit { .. } => -32009,
332            Self::ResourceExhausted { .. } => -32010,
333            Self::Timeout { .. } => -32603,
334            Self::Handler { .. } => -32002,
335            Self::Transport(_) => -32603,
336            Self::Configuration { .. } => -32015,
337            Self::Lifecycle(_) => -32603,
338            Self::Shutdown(_) => -32603,
339            Self::Middleware { .. } => -32603,
340            Self::Registry(_) => -32603,
341            Self::Routing { .. } => -32603,
342            Self::Internal(_) => -32603,
343            Self::Io(_) => -32603,
344            Self::Serialization(_) => -32602,
345        };
346        tracing::info!(
347            "🔍 [ServerError::error_code] Returning code: {} for variant: {:?}",
348            code,
349            std::mem::discriminant(self)
350        );
351        code
352    }
353}
354
355/// Error recovery strategies
356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357pub enum ErrorRecovery {
358    /// Retry the operation
359    Retry,
360    /// Skip and continue
361    Skip,
362    /// Fail immediately
363    Fail,
364    /// Graceful degradation
365    Degrade,
366}
367
368/// Error context for detailed error reporting
369#[derive(Debug, Clone)]
370pub struct ErrorContext {
371    /// Error category
372    pub category: String,
373    /// Operation being performed
374    pub operation: String,
375    /// Request ID if applicable
376    pub request_id: Option<String>,
377    /// Client ID if applicable
378    pub client_id: Option<String>,
379    /// Additional metadata
380    pub metadata: std::collections::HashMap<String, String>,
381}
382
383impl ErrorContext {
384    /// Create a new error context
385    pub fn new(category: impl Into<String>, operation: impl Into<String>) -> Self {
386        Self {
387            category: category.into(),
388            operation: operation.into(),
389            request_id: None,
390            client_id: None,
391            metadata: std::collections::HashMap::new(),
392        }
393    }
394
395    /// Add request ID to context
396    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
397        self.request_id = Some(request_id.into());
398        self
399    }
400
401    /// Add client ID to context
402    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
403        self.client_id = Some(client_id.into());
404        self
405    }
406
407    /// Add metadata to context
408    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
409        self.metadata.insert(key.into(), value.into());
410        self
411    }
412}
413
414// Conversion from core errors to server errors
415impl From<Box<turbomcp_protocol::Error>> for ServerError {
416    fn from(core_error: Box<turbomcp_protocol::Error>) -> Self {
417        use turbomcp_protocol::ErrorKind;
418
419        match core_error.kind {
420            // MCP-specific errors
421            ErrorKind::UserRejected => Self::Handler {
422                message: core_error.message,
423                context: core_error.context.operation,
424            },
425            ErrorKind::ToolNotFound | ErrorKind::PromptNotFound | ErrorKind::ResourceNotFound => {
426                Self::NotFound {
427                    resource: core_error.message,
428                }
429            }
430            ErrorKind::ToolExecutionFailed => Self::Handler {
431                message: core_error.message,
432                context: core_error.context.operation,
433            },
434            ErrorKind::ResourceAccessDenied => Self::Authorization {
435                message: core_error.message,
436                resource: core_error.context.component,
437            },
438            ErrorKind::CapabilityNotSupported => Self::Handler {
439                message: format!("Capability not supported: {}", core_error.message),
440                context: None,
441            },
442            ErrorKind::ProtocolVersionMismatch => Self::Configuration {
443                message: core_error.message,
444                key: Some("protocol_version".to_string()),
445            },
446            ErrorKind::ServerOverloaded => Self::ResourceExhausted {
447                resource: "server_capacity".to_string(),
448                current: None,
449                max: None,
450            },
451
452            // Deprecated (backwards compatibility)
453            #[allow(deprecated)]
454            ErrorKind::Handler => Self::Handler {
455                message: core_error.message,
456                context: core_error.context.operation,
457            },
458            #[allow(deprecated)]
459            ErrorKind::NotFound => Self::NotFound {
460                resource: core_error.message,
461            },
462
463            // General errors
464            ErrorKind::Authentication => Self::Authentication {
465                message: core_error.message,
466                method: None,
467            },
468            ErrorKind::PermissionDenied => Self::Authorization {
469                message: core_error.message,
470                resource: None,
471            },
472            ErrorKind::BadRequest | ErrorKind::Validation => Self::Handler {
473                message: format!("Validation error: {}", core_error.message),
474                context: None,
475            },
476            ErrorKind::Timeout => Self::Timeout {
477                operation: core_error
478                    .context
479                    .operation
480                    .unwrap_or_else(|| "unknown".to_string()),
481                timeout_ms: 30000, // Default timeout
482            },
483            ErrorKind::RateLimited => Self::RateLimit {
484                message: core_error.message,
485                retry_after: None,
486            },
487            ErrorKind::Configuration => Self::Configuration {
488                message: core_error.message,
489                key: None,
490            },
491            ErrorKind::Transport => {
492                Self::Internal(format!("Transport error: {}", core_error.message))
493            }
494            ErrorKind::Serialization => {
495                Self::Internal(format!("Serialization error: {}", core_error.message))
496            }
497            ErrorKind::Protocol => {
498                Self::Internal(format!("Protocol error: {}", core_error.message))
499            }
500            ErrorKind::Unavailable => Self::ResourceExhausted {
501                resource: "service".to_string(),
502                current: None,
503                max: None,
504            },
505            ErrorKind::ExternalService => {
506                Self::Internal(format!("External service error: {}", core_error.message))
507            }
508            ErrorKind::Cancelled => {
509                Self::Internal(format!("Operation cancelled: {}", core_error.message))
510            }
511            ErrorKind::Internal => Self::Internal(core_error.message),
512            ErrorKind::Security => Self::Authorization {
513                message: format!("Security error: {}", core_error.message),
514                resource: None,
515            },
516        }
517    }
518}
519
520// Conversion from server errors to protocol errors
521///
522/// This conversion preserves protocol errors directly when they come from clients
523/// (ServerError::Protocol variant), ensuring error codes like `-1` for user rejection
524/// are maintained through the server layer.
525///
526/// # Error Code Preservation
527///
528/// When a client returns an error (e.g., user rejects sampling with code `-1`),
529/// the server receives it as `ServerError::Protocol(Error{ kind: UserRejected })`.
530/// This conversion unwraps it directly, preserving the original error code when
531/// the error is sent back to calling clients.
532impl From<ServerError> for Box<turbomcp_protocol::Error> {
533    fn from(server_error: ServerError) -> Self {
534        match server_error {
535            // Unwrap protocol errors directly to preserve error codes
536            ServerError::Protocol(protocol_err) => protocol_err,
537
538            // Map other server errors to appropriate protocol errors
539            ServerError::Transport(transport_err) => {
540                turbomcp_protocol::Error::transport(format!("Transport error: {}", transport_err))
541            }
542            ServerError::Handler { message, context } => {
543                turbomcp_protocol::Error::internal(format!(
544                    "Handler error{}: {}",
545                    context
546                        .as_ref()
547                        .map(|c| format!(" ({})", c))
548                        .unwrap_or_default(),
549                    message
550                ))
551            }
552            ServerError::Core(err) => {
553                turbomcp_protocol::Error::internal(format!("Core error: {}", err))
554            }
555            ServerError::Configuration { message, .. } => {
556                turbomcp_protocol::Error::configuration(message)
557            }
558            ServerError::Authentication { message, .. } => {
559                turbomcp_protocol::Error::new(turbomcp_protocol::ErrorKind::Authentication, message)
560            }
561            ServerError::Authorization { message, .. } => turbomcp_protocol::Error::new(
562                turbomcp_protocol::ErrorKind::PermissionDenied,
563                message,
564            ),
565            ServerError::RateLimit { message, .. } => {
566                turbomcp_protocol::Error::rate_limited(message)
567            }
568            ServerError::Timeout {
569                operation,
570                timeout_ms,
571            } => turbomcp_protocol::Error::timeout(format!(
572                "Operation '{}' timed out after {}ms",
573                operation, timeout_ms
574            )),
575            ServerError::NotFound { resource } => turbomcp_protocol::Error::new(
576                turbomcp_protocol::ErrorKind::ResourceNotFound,
577                format!("Resource not found: {}", resource),
578            ),
579            ServerError::ResourceExhausted { resource, .. } => turbomcp_protocol::Error::new(
580                turbomcp_protocol::ErrorKind::Unavailable,
581                format!("Resource exhausted: {}", resource),
582            ),
583            ServerError::Internal(message) => turbomcp_protocol::Error::internal(message),
584            ServerError::Lifecycle(message) => {
585                turbomcp_protocol::Error::internal(format!("Lifecycle error: {}", message))
586            }
587            ServerError::Shutdown(message) => {
588                turbomcp_protocol::Error::internal(format!("Shutdown error: {}", message))
589            }
590            ServerError::Middleware { name, message } => turbomcp_protocol::Error::internal(
591                format!("Middleware error ({}): {}", name, message),
592            ),
593            ServerError::Registry(message) => {
594                turbomcp_protocol::Error::internal(format!("Registry error: {}", message))
595            }
596            ServerError::Routing { message, .. } => {
597                turbomcp_protocol::Error::internal(format!("Routing error: {}", message))
598            }
599            ServerError::Io(err) => {
600                turbomcp_protocol::Error::internal(format!("IO error: {}", err))
601            }
602            ServerError::Serialization(err) => turbomcp_protocol::Error::new(
603                turbomcp_protocol::ErrorKind::Serialization,
604                format!("Serialization error: {}", err),
605            ),
606        }
607    }
608}
609
610// Comprehensive tests in separate file (tokio/axum pattern)
611#[cfg(test)]
612mod tests;