1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10use uuid::Uuid;
11
12#[cfg(feature = "fancy-errors")]
13use miette::Diagnostic;
14
15pub type Result<T> = std::result::Result<T, Box<Error>>;
17
18#[derive(Debug, Serialize)]
20#[cfg_attr(feature = "fancy-errors", derive(Diagnostic))]
21pub struct Error {
22 pub id: Uuid,
24
25 pub kind: ErrorKind,
27
28 pub message: String,
30
31 pub context: ErrorContext,
33
34 #[serde(skip)]
36 pub source: Option<Box<Error>>,
37
38 #[cfg(debug_assertions)]
40 #[serde(skip)]
41 pub backtrace: std::backtrace::Backtrace,
42}
43
44impl Clone for Error {
45 fn clone(&self) -> Self {
46 Self {
47 id: self.id,
48 kind: self.kind,
49 message: self.message.clone(),
50 context: self.context.clone(),
51 source: self.source.clone(),
52 #[cfg(debug_assertions)]
53 backtrace: std::backtrace::Backtrace::capture(),
54 }
55 }
56}
57
58impl<'de> Deserialize<'de> for Error {
59 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
60 where
61 D: serde::Deserializer<'de>,
62 {
63 #[derive(Deserialize)]
64 struct ErrorData {
65 id: Uuid,
66 kind: ErrorKind,
67 message: String,
68 context: ErrorContext,
69 }
70
71 let data = ErrorData::deserialize(deserializer)?;
72 Ok(Self {
73 id: data.id,
74 kind: data.kind,
75 message: data.message,
76 context: data.context,
77 source: None,
78 #[cfg(debug_assertions)]
79 backtrace: std::backtrace::Backtrace::capture(),
80 })
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum ErrorKind {
88 ToolNotFound,
93
94 ToolExecutionFailed,
96
97 PromptNotFound,
99
100 ResourceNotFound,
102
103 ResourceAccessDenied,
105
106 CapabilityNotSupported,
108
109 ProtocolVersionMismatch,
111
112 UserRejected,
118
119 Validation,
124
125 BadRequest,
127
128 Internal,
130
131 Serialization,
133
134 Protocol,
136
137 Authentication,
142
143 PermissionDenied,
145
146 Transport,
148
149 Timeout,
151
152 Unavailable,
154
155 RateLimited,
157
158 ServerOverloaded,
160
161 Configuration,
163
164 ExternalService,
166
167 Cancelled,
169
170 Security,
172
173 #[deprecated(
183 since = "2.1.0",
184 note = "Use specific error kinds: ToolExecutionFailed, PromptNotFound, ResourceNotFound, etc."
185 )]
186 Handler,
187
188 #[deprecated(
195 since = "2.1.0",
196 note = "Use specific error kinds: ToolNotFound, PromptNotFound, ResourceNotFound"
197 )]
198 NotFound,
199}
200
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct ErrorContext {
204 pub operation: Option<String>,
206
207 pub component: Option<String>,
209
210 pub request_id: Option<String>,
212
213 pub user_id: Option<String>,
215
216 pub metadata: HashMap<String, serde_json::Value>,
218
219 pub timestamp: chrono::DateTime<chrono::Utc>,
221
222 pub retry_info: Option<RetryInfo>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct RetryInfo {
229 pub attempts: u32,
231
232 pub max_attempts: u32,
234
235 pub retry_after_ms: Option<u64>,
237}
238
239impl Error {
240 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Box<Self> {
242 Box::new(Self {
243 id: Uuid::new_v4(),
244 kind,
245 message: message.into(),
246 context: ErrorContext {
247 timestamp: chrono::Utc::now(),
248 ..Default::default()
249 },
250 source: None,
251 #[cfg(debug_assertions)]
252 backtrace: std::backtrace::Backtrace::capture(),
253 })
254 }
255
256 pub fn validation(message: impl Into<String>) -> Box<Self> {
258 Self::new(ErrorKind::Validation, message)
259 }
260
261 pub fn invalid_params(message: impl Into<String>) -> Box<Self> {
276 Self::new(ErrorKind::Validation, message)
277 }
278
279 pub fn authentication(message: impl Into<String>) -> Box<Self> {
281 Self::new(ErrorKind::Authentication, message)
282 }
283
284 #[deprecated(
286 since = "2.1.0",
287 note = "Use specific constructors: tool_not_found(), prompt_not_found(), or resource_not_found()"
288 )]
289 pub fn not_found(message: impl Into<String>) -> Box<Self> {
290 #[allow(deprecated)]
291 Self::new(ErrorKind::NotFound, message)
292 }
293
294 pub fn permission_denied(message: impl Into<String>) -> Box<Self> {
296 Self::new(ErrorKind::PermissionDenied, message)
297 }
298
299 pub fn bad_request(message: impl Into<String>) -> Box<Self> {
301 Self::new(ErrorKind::BadRequest, message)
302 }
303
304 pub fn internal(message: impl Into<String>) -> Box<Self> {
306 Self::new(ErrorKind::Internal, message)
307 }
308
309 pub fn transport(message: impl Into<String>) -> Box<Self> {
311 Self::new(ErrorKind::Transport, message)
312 }
313
314 pub fn serialization(message: impl Into<String>) -> Box<Self> {
316 Self::new(ErrorKind::Serialization, message)
317 }
318
319 pub fn protocol(message: impl Into<String>) -> Box<Self> {
321 Self::new(ErrorKind::Protocol, message)
322 }
323
324 #[must_use]
329 pub fn rpc(code: i32, message: &str) -> Box<Self> {
330 let kind = match code {
332 -1 => ErrorKind::UserRejected, -32001 => ErrorKind::ToolNotFound, -32002 => ErrorKind::ToolExecutionFailed, -32003 => ErrorKind::PromptNotFound, -32004 => ErrorKind::ResourceNotFound, -32005 => ErrorKind::ResourceAccessDenied, -32006 => ErrorKind::CapabilityNotSupported, -32007 => ErrorKind::ProtocolVersionMismatch, -32008 => ErrorKind::Authentication, -32009 => ErrorKind::RateLimited, -32010 => ErrorKind::ServerOverloaded, -32600 => ErrorKind::BadRequest, -32601 => ErrorKind::Protocol, -32602 => ErrorKind::Validation, -32603 => ErrorKind::Internal, _ => ErrorKind::Protocol, };
349
350 Self::new(kind, format!("RPC error {code}: {message}"))
351 }
352
353 pub fn timeout(message: impl Into<String>) -> Box<Self> {
355 Self::new(ErrorKind::Timeout, message)
356 }
357
358 pub fn unavailable(message: impl Into<String>) -> Box<Self> {
360 Self::new(ErrorKind::Unavailable, message)
361 }
362
363 pub fn rate_limited(message: impl Into<String>) -> Box<Self> {
365 Self::new(ErrorKind::RateLimited, message)
366 }
367
368 pub fn configuration(message: impl Into<String>) -> Box<Self> {
370 Self::new(ErrorKind::Configuration, message)
371 }
372
373 pub fn external_service(message: impl Into<String>) -> Box<Self> {
375 Self::new(ErrorKind::ExternalService, message)
376 }
377
378 pub fn cancelled(message: impl Into<String>) -> Box<Self> {
380 Self::new(ErrorKind::Cancelled, message)
381 }
382
383 pub fn user_rejected(message: impl Into<String>) -> Box<Self> {
389 Self::new(ErrorKind::UserRejected, message)
390 }
391
392 #[deprecated(
394 since = "2.1.0",
395 note = "Use specific error constructors: tool_not_found(), tool_execution_failed(), etc."
396 )]
397 pub fn handler(message: impl Into<String>) -> Box<Self> {
398 #[allow(deprecated)]
399 Self::new(ErrorKind::Handler, message)
400 }
401
402 pub fn security(message: impl Into<String>) -> Box<Self> {
404 Self::new(ErrorKind::Security, message)
405 }
406
407 pub fn tool_not_found(tool_name: impl Into<String>) -> Box<Self> {
421 Self::new(
422 ErrorKind::ToolNotFound,
423 format!("Tool not found: {}", tool_name.into()),
424 )
425 .with_operation("tool_lookup")
426 .with_component("tool_registry")
427 }
428
429 pub fn tool_execution_failed(
439 tool_name: impl Into<String>,
440 reason: impl Into<String>,
441 ) -> Box<Self> {
442 Self::new(
443 ErrorKind::ToolExecutionFailed,
444 format!("Tool '{}' failed: {}", tool_name.into(), reason.into()),
445 )
446 .with_operation("tool_execution")
447 }
448
449 pub fn prompt_not_found(prompt_name: impl Into<String>) -> Box<Self> {
459 Self::new(
460 ErrorKind::PromptNotFound,
461 format!("Prompt not found: {}", prompt_name.into()),
462 )
463 .with_operation("prompt_lookup")
464 .with_component("prompt_registry")
465 }
466
467 pub fn resource_not_found(uri: impl Into<String>) -> Box<Self> {
477 Self::new(
478 ErrorKind::ResourceNotFound,
479 format!("Resource not found: {}", uri.into()),
480 )
481 .with_operation("resource_lookup")
482 .with_component("resource_provider")
483 }
484
485 pub fn resource_access_denied(uri: impl Into<String>, reason: impl Into<String>) -> Box<Self> {
495 Self::new(
496 ErrorKind::ResourceAccessDenied,
497 format!(
498 "Access denied to resource '{}': {}",
499 uri.into(),
500 reason.into()
501 ),
502 )
503 .with_operation("resource_access")
504 .with_component("resource_security")
505 }
506
507 pub fn capability_not_supported(capability: impl Into<String>) -> Box<Self> {
517 Self::new(
518 ErrorKind::CapabilityNotSupported,
519 format!("Capability not supported: {}", capability.into()),
520 )
521 .with_operation("capability_check")
522 }
523
524 pub fn protocol_version_mismatch(
534 client_version: impl Into<String>,
535 server_version: impl Into<String>,
536 ) -> Box<Self> {
537 Self::new(
538 ErrorKind::ProtocolVersionMismatch,
539 format!(
540 "Protocol version mismatch: client={}, server={}",
541 client_version.into(),
542 server_version.into()
543 ),
544 )
545 .with_operation("version_negotiation")
546 }
547
548 pub fn server_overloaded() -> Box<Self> {
558 Self::new(
559 ErrorKind::ServerOverloaded,
560 "Server is currently overloaded",
561 )
562 .with_operation("request_processing")
563 }
564
565 #[must_use]
567 pub fn with_context(
568 mut self: Box<Self>,
569 key: impl Into<String>,
570 value: impl Into<serde_json::Value>,
571 ) -> Box<Self> {
572 self.context.metadata.insert(key.into(), value.into());
573 self
574 }
575
576 #[must_use]
578 pub fn with_operation(mut self: Box<Self>, operation: impl Into<String>) -> Box<Self> {
579 self.context.operation = Some(operation.into());
580 self
581 }
582
583 #[must_use]
585 pub fn with_component(mut self: Box<Self>, component: impl Into<String>) -> Box<Self> {
586 self.context.component = Some(component.into());
587 self
588 }
589
590 #[must_use]
592 pub fn with_request_id(mut self: Box<Self>, request_id: impl Into<String>) -> Box<Self> {
593 self.context.request_id = Some(request_id.into());
594 self
595 }
596
597 #[must_use]
599 pub fn with_user_id(mut self: Box<Self>, user_id: impl Into<String>) -> Box<Self> {
600 self.context.user_id = Some(user_id.into());
601 self
602 }
603
604 #[must_use]
606 pub fn with_retry_info(mut self: Box<Self>, retry_info: RetryInfo) -> Box<Self> {
607 self.context.retry_info = Some(retry_info);
608 self
609 }
610
611 #[must_use]
613 pub fn with_source(mut self: Box<Self>, source: Box<Self>) -> Box<Self> {
614 self.source = Some(source);
615 self
616 }
617
618 pub const fn is_retryable(&self) -> bool {
620 matches!(
621 self.kind,
622 ErrorKind::Timeout
623 | ErrorKind::Unavailable
624 | ErrorKind::Transport
625 | ErrorKind::ExternalService
626 | ErrorKind::RateLimited
627 )
628 }
629
630 pub const fn is_temporary(&self) -> bool {
632 matches!(
633 self.kind,
634 ErrorKind::Timeout
635 | ErrorKind::Unavailable
636 | ErrorKind::RateLimited
637 | ErrorKind::ExternalService
638 )
639 }
640
641 pub const fn http_status_code(&self) -> u16 {
643 match self.kind {
644 ErrorKind::Validation | ErrorKind::BadRequest | ErrorKind::UserRejected => 400,
646 ErrorKind::Authentication => 401,
647 ErrorKind::PermissionDenied | ErrorKind::Security | ErrorKind::ResourceAccessDenied => {
648 403
649 }
650 ErrorKind::ToolNotFound | ErrorKind::PromptNotFound | ErrorKind::ResourceNotFound => {
651 404
652 }
653 ErrorKind::Timeout => 408,
654 ErrorKind::RateLimited => 429,
655 ErrorKind::Cancelled => 499, ErrorKind::Internal
659 | ErrorKind::Configuration
660 | ErrorKind::Serialization
661 | ErrorKind::Protocol
662 | ErrorKind::ToolExecutionFailed
663 | ErrorKind::CapabilityNotSupported
664 | ErrorKind::ProtocolVersionMismatch => 500,
665
666 ErrorKind::Transport
667 | ErrorKind::ExternalService
668 | ErrorKind::Unavailable
669 | ErrorKind::ServerOverloaded => 503,
670
671 #[allow(deprecated)]
673 ErrorKind::Handler => 500,
674 #[allow(deprecated)]
675 ErrorKind::NotFound => 404,
676 }
677 }
678
679 pub const fn jsonrpc_error_code(&self) -> i32 {
681 match self.kind {
682 ErrorKind::BadRequest => -32600, ErrorKind::Protocol => -32601, ErrorKind::Validation | ErrorKind::Serialization => -32602, ErrorKind::Internal => -32603, ErrorKind::UserRejected => -1, ErrorKind::ToolNotFound => -32001, ErrorKind::ToolExecutionFailed => -32002, ErrorKind::PromptNotFound => -32003, ErrorKind::ResourceNotFound => -32004, ErrorKind::ResourceAccessDenied => -32005, ErrorKind::CapabilityNotSupported => -32006, ErrorKind::ProtocolVersionMismatch => -32007, ErrorKind::Authentication => -32008, ErrorKind::RateLimited => -32009, ErrorKind::ServerOverloaded => -32010, ErrorKind::PermissionDenied => -32011, ErrorKind::Timeout => -32012, ErrorKind::Unavailable => -32013, ErrorKind::Transport => -32014, ErrorKind::Configuration => -32015, ErrorKind::ExternalService => -32016, ErrorKind::Cancelled => -32017, ErrorKind::Security => -32018, #[allow(deprecated)]
713 ErrorKind::Handler => -32019, #[allow(deprecated)]
715 ErrorKind::NotFound => -32020, }
717 }
718}
719
720impl fmt::Display for Error {
721 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
722 write!(f, "{}", self.message)?;
723
724 if let Some(operation) = &self.context.operation {
725 write!(f, " (operation: {operation})")?;
726 }
727
728 if let Some(component) = &self.context.component {
729 write!(f, " (component: {component})")?;
730 }
731
732 if let Some(request_id) = &self.context.request_id {
733 write!(f, " (request_id: {request_id})")?;
734 }
735
736 Ok(())
737 }
738}
739
740impl std::error::Error for Error {
741 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
742 None
746 }
747}
748
749impl ErrorKind {
750 #[must_use]
752 pub const fn description(self) -> &'static str {
753 match self {
754 Self::UserRejected => "User rejected request",
756 Self::ToolNotFound => "Tool not found",
757 Self::ToolExecutionFailed => "Tool execution failed",
758 Self::PromptNotFound => "Prompt not found",
759 Self::ResourceNotFound => "Resource not found",
760 Self::ResourceAccessDenied => "Resource access denied",
761 Self::CapabilityNotSupported => "Capability not supported",
762 Self::ProtocolVersionMismatch => "Protocol version mismatch",
763
764 Self::Validation => "Input validation failed",
766 Self::BadRequest => "Bad request",
767 Self::Internal => "Internal server error",
768 Self::Serialization => "Serialization error",
769 Self::Protocol => "Protocol error",
770
771 Self::Authentication => "Authentication failed",
773 Self::PermissionDenied => "Permission denied",
774 Self::Transport => "Transport error",
775 Self::Timeout => "Operation timed out",
776 Self::Unavailable => "Service unavailable",
777 Self::RateLimited => "Rate limit exceeded",
778 Self::ServerOverloaded => "Server overloaded",
779 Self::Configuration => "Configuration error",
780 Self::ExternalService => "External service error",
781 Self::Cancelled => "Operation cancelled",
782 Self::Security => "Security constraint violation",
783
784 #[allow(deprecated)]
786 Self::Handler => "Handler execution error (deprecated)",
787 #[allow(deprecated)]
788 Self::NotFound => "Resource not found (deprecated)",
789 }
790 }
791}
792
793impl fmt::Display for ErrorKind {
794 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795 write!(f, "{}", self.description())
796 }
797}
798
799#[macro_export]
801macro_rules! mcp_error {
802 ($kind:expr, $message:expr) => {
803 $crate::error::Error::new($kind, $message)
804 };
805 ($kind:expr, $message:expr, $($key:expr => $value:expr),+) => {
806 {
807 let mut error = $crate::error::Error::new($kind, $message);
808 $(
809 error = error.with_context($key, $value);
810 )+
811 error
812 }
813 };
814}
815
816pub trait ErrorExt<T> {
818 fn with_mcp_error(self, kind: ErrorKind, message: impl Into<String>) -> Result<T>;
824
825 fn with_internal_error(self, message: impl Into<String>) -> Result<T>;
831}
832
833impl<T, E> ErrorExt<T> for std::result::Result<T, E>
834where
835 E: std::error::Error + Send + Sync + 'static,
836{
837 fn with_mcp_error(self, kind: ErrorKind, message: impl Into<String>) -> Result<T> {
838 self.map_err(|e| {
839 Error::new(kind, format!("{}: {}", message.into(), e))
840 .with_context("source_error", e.to_string())
841 })
842 }
843
844 fn with_internal_error(self, message: impl Into<String>) -> Result<T> {
845 self.with_mcp_error(ErrorKind::Internal, message)
846 }
847}
848
849impl From<serde_json::Error> for Box<Error> {
851 fn from(err: serde_json::Error) -> Self {
852 Error::serialization(format!("JSON serialization error: {err}"))
853 }
854}
855
856impl From<std::io::Error> for Box<Error> {
857 fn from(err: std::io::Error) -> Self {
858 Error::transport(format!("IO error: {err}"))
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn test_error_creation() {
868 let error = Error::validation("Invalid input");
869 assert_eq!(error.kind, ErrorKind::Validation);
870 assert_eq!(error.message, "Invalid input");
871 }
872
873 #[test]
874 fn test_error_context() {
875 let error = Error::internal("Something went wrong")
876 .with_operation("test_operation")
877 .with_component("test_component")
878 .with_request_id("req-123")
879 .with_context("key", "value");
880
881 assert_eq!(error.context.operation, Some("test_operation".to_string()));
882 assert_eq!(error.context.component, Some("test_component".to_string()));
883 assert_eq!(error.context.request_id, Some("req-123".to_string()));
884 assert_eq!(
885 error.context.metadata.get("key"),
886 Some(&serde_json::Value::String("value".to_string()))
887 );
888 }
889
890 #[test]
891 fn test_error_properties() {
892 let retryable_error = Error::timeout("Request timed out");
893 assert!(retryable_error.is_retryable());
894 assert!(retryable_error.is_temporary());
895
896 let permanent_error = Error::validation("Invalid data");
897 assert!(!permanent_error.is_retryable());
898 assert!(!permanent_error.is_temporary());
899 }
900
901 #[test]
902 fn test_http_status_codes() {
903 assert_eq!(Error::validation("test").http_status_code(), 400);
904 assert_eq!(Error::tool_not_found("test").http_status_code(), 404);
905 assert_eq!(Error::internal("test").http_status_code(), 500);
906 }
907
908 #[test]
909 fn test_error_macro() {
910 let error = mcp_error!(ErrorKind::Validation, "test message");
911 assert_eq!(error.kind, ErrorKind::Validation);
912 assert_eq!(error.message, "test message");
913
914 let error_with_context = mcp_error!(
915 ErrorKind::Internal,
916 "test message",
917 "key1" => "value1",
918 "key2" => 42
919 );
920 assert_eq!(error_with_context.context.metadata.len(), 2);
921 }
922}