1use alloc::boxed::Box;
29use alloc::string::String;
30use core::fmt;
31use serde::{Deserialize, Serialize};
32
33pub type McpResult<T> = core::result::Result<T, McpError>;
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct McpError {
46 #[cfg(feature = "rich-errors")]
48 pub id: uuid::Uuid,
49 pub kind: ErrorKind,
51 pub message: String,
53 #[serde(skip_serializing)]
56 pub source_location: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub context: Option<alloc::boxed::Box<ErrorContext>>,
60 #[cfg(feature = "rich-errors")]
62 pub timestamp: chrono::DateTime<chrono::Utc>,
63}
64
65#[derive(Debug, Clone, Default, Serialize, Deserialize)]
67pub struct ErrorContext {
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub operation: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub component: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub request_id: Option<String>,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
84#[serde(rename_all = "snake_case")]
85#[non_exhaustive]
86pub enum ErrorKind {
87 ToolNotFound,
90 ToolExecutionFailed,
92 PromptNotFound,
94 ResourceNotFound,
96 ResourceAccessDenied,
98 CapabilityNotSupported,
100 ProtocolVersionMismatch,
102 UrlElicitationRequired,
104 UserRejected,
106
107 ParseError,
110 InvalidRequest,
112 MethodNotFound,
114 InvalidParams,
116 Internal,
118
119 Authentication,
122 PermissionDenied,
124 Transport,
126 Timeout,
128 Unavailable,
130 RateLimited,
132 ServerOverloaded,
134 Configuration,
136 ExternalService,
138 Cancelled,
140 Security,
142 Serialization,
144}
145
146impl McpError {
147 #[must_use]
149 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
150 Self {
151 #[cfg(feature = "rich-errors")]
152 id: uuid::Uuid::new_v4(),
153 kind,
154 message: message.into(),
155 source_location: None,
156 context: None,
157 #[cfg(feature = "rich-errors")]
158 timestamp: chrono::Utc::now(),
159 }
160 }
161
162 #[cfg(feature = "rich-errors")]
164 #[must_use]
165 pub const fn id(&self) -> uuid::Uuid {
166 self.id
167 }
168
169 #[cfg(feature = "rich-errors")]
171 #[must_use]
172 pub const fn timestamp(&self) -> chrono::DateTime<chrono::Utc> {
173 self.timestamp
174 }
175
176 #[must_use]
178 pub fn invalid_params(message: impl Into<String>) -> Self {
179 Self::new(ErrorKind::InvalidParams, message)
180 }
181
182 #[must_use]
184 pub fn internal(message: impl Into<String>) -> Self {
185 Self::new(ErrorKind::Internal, message)
186 }
187
188 #[must_use]
204 pub fn safe_internal(message: impl Into<String>) -> Self {
205 let sanitized = crate::security::sanitize_error_message(&message.into());
206 Self::new(ErrorKind::Internal, sanitized)
207 }
208
209 #[must_use]
213 pub fn safe_tool_execution_failed(
214 tool_name: impl Into<String>,
215 reason: impl Into<String>,
216 ) -> Self {
217 let name = tool_name.into();
218 let sanitized_reason = crate::security::sanitize_error_message(&reason.into());
219 Self::new(
220 ErrorKind::ToolExecutionFailed,
221 alloc::format!("Tool '{}' failed: {}", name, sanitized_reason),
222 )
223 .with_operation("tool_execution")
224 }
225
226 #[must_use]
231 pub fn sanitized(mut self) -> Self {
232 self.message = crate::security::sanitize_error_message(&self.message);
233 self
234 }
235
236 #[must_use]
238 pub fn parse_error(message: impl Into<String>) -> Self {
239 Self::new(ErrorKind::ParseError, message)
240 }
241
242 #[must_use]
244 pub fn invalid_request(message: impl Into<String>) -> Self {
245 Self::new(ErrorKind::InvalidRequest, message)
246 }
247
248 #[must_use]
250 pub fn method_not_found(method: impl Into<String>) -> Self {
251 let method = method.into();
252 Self::new(
253 ErrorKind::MethodNotFound,
254 alloc::format!("Method not found: {}", method),
255 )
256 }
257
258 #[must_use]
260 pub fn tool_not_found(tool_name: impl Into<String>) -> Self {
261 let name = tool_name.into();
262 Self::new(
263 ErrorKind::ToolNotFound,
264 alloc::format!("Tool not found: {}", name),
265 )
266 .with_operation("tool_lookup")
267 .with_component("tool_registry")
268 }
269
270 #[must_use]
272 pub fn tool_execution_failed(tool_name: impl Into<String>, reason: impl Into<String>) -> Self {
273 let name = tool_name.into();
274 let reason = reason.into();
275 Self::new(
276 ErrorKind::ToolExecutionFailed,
277 alloc::format!("Tool '{}' failed: {}", name, reason),
278 )
279 .with_operation("tool_execution")
280 }
281
282 #[must_use]
284 pub fn prompt_not_found(prompt_name: impl Into<String>) -> Self {
285 let name = prompt_name.into();
286 Self::new(
287 ErrorKind::PromptNotFound,
288 alloc::format!("Prompt not found: {}", name),
289 )
290 .with_operation("prompt_lookup")
291 .with_component("prompt_registry")
292 }
293
294 #[must_use]
296 pub fn resource_not_found(uri: impl Into<String>) -> Self {
297 let uri = uri.into();
298 Self::new(
299 ErrorKind::ResourceNotFound,
300 alloc::format!("Resource not found: {}", uri),
301 )
302 .with_operation("resource_lookup")
303 .with_component("resource_provider")
304 }
305
306 #[must_use]
308 pub fn resource_access_denied(uri: impl Into<String>, reason: impl Into<String>) -> Self {
309 let uri = uri.into();
310 let reason = reason.into();
311 Self::new(
312 ErrorKind::ResourceAccessDenied,
313 alloc::format!("Access denied to '{}': {}", uri, reason),
314 )
315 .with_operation("resource_access")
316 .with_component("resource_security")
317 }
318
319 #[must_use]
321 pub fn capability_not_supported(capability: impl Into<String>) -> Self {
322 let cap = capability.into();
323 Self::new(
324 ErrorKind::CapabilityNotSupported,
325 alloc::format!("Capability not supported: {}", cap),
326 )
327 }
328
329 #[must_use]
331 pub fn protocol_version_mismatch(
332 client_version: impl Into<String>,
333 server_version: impl Into<String>,
334 ) -> Self {
335 let client = client_version.into();
336 let server = server_version.into();
337 Self::new(
338 ErrorKind::ProtocolVersionMismatch,
339 alloc::format!(
340 "Protocol version mismatch: client={}, server={}",
341 client,
342 server
343 ),
344 )
345 }
346
347 #[must_use]
349 pub fn timeout(message: impl Into<String>) -> Self {
350 Self::new(ErrorKind::Timeout, message)
351 }
352
353 #[must_use]
355 pub fn transport(message: impl Into<String>) -> Self {
356 Self::new(ErrorKind::Transport, message)
357 }
358
359 #[must_use]
361 pub fn authentication(message: impl Into<String>) -> Self {
362 Self::new(ErrorKind::Authentication, message)
363 }
364
365 #[must_use]
367 pub fn permission_denied(message: impl Into<String>) -> Self {
368 Self::new(ErrorKind::PermissionDenied, message)
369 }
370
371 #[must_use]
373 pub fn rate_limited(message: impl Into<String>) -> Self {
374 Self::new(ErrorKind::RateLimited, message)
375 }
376
377 #[must_use]
379 pub fn cancelled(message: impl Into<String>) -> Self {
380 Self::new(ErrorKind::Cancelled, message)
381 }
382
383 #[must_use]
385 pub fn user_rejected(message: impl Into<String>) -> Self {
386 Self::new(ErrorKind::UserRejected, message)
387 }
388
389 #[must_use]
391 pub fn serialization(message: impl Into<String>) -> Self {
392 Self::new(ErrorKind::Serialization, message)
393 }
394
395 #[must_use]
397 pub fn security(message: impl Into<String>) -> Self {
398 Self::new(ErrorKind::Security, message)
399 }
400
401 #[must_use]
403 pub fn unavailable(message: impl Into<String>) -> Self {
404 Self::new(ErrorKind::Unavailable, message)
405 }
406
407 #[must_use]
409 pub fn configuration(message: impl Into<String>) -> Self {
410 Self::new(ErrorKind::Configuration, message)
411 }
412
413 #[must_use]
415 pub fn external_service(message: impl Into<String>) -> Self {
416 Self::new(ErrorKind::ExternalService, message)
417 }
418
419 #[must_use]
421 pub fn server_overloaded() -> Self {
422 Self::new(
423 ErrorKind::ServerOverloaded,
424 "Server is currently overloaded",
425 )
426 }
427
428 #[must_use]
430 pub fn from_rpc_code(code: i32, message: impl Into<String>) -> Self {
431 Self::new(ErrorKind::from_i32(code), message)
432 }
433
434 #[must_use]
436 pub fn with_operation(mut self, operation: impl Into<String>) -> Self {
437 let ctx = self
438 .context
439 .get_or_insert_with(|| alloc::boxed::Box::new(ErrorContext::default()));
440 ctx.operation = Some(operation.into());
441 self
442 }
443
444 #[must_use]
446 pub fn with_component(mut self, component: impl Into<String>) -> Self {
447 let ctx = self
448 .context
449 .get_or_insert_with(|| alloc::boxed::Box::new(ErrorContext::default()));
450 ctx.component = Some(component.into());
451 self
452 }
453
454 #[must_use]
456 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
457 let ctx = self
458 .context
459 .get_or_insert_with(|| alloc::boxed::Box::new(ErrorContext::default()));
460 ctx.request_id = Some(request_id.into());
461 self
462 }
463
464 #[must_use]
466 pub fn with_source_location(mut self, location: impl Into<String>) -> Self {
467 self.source_location = Some(location.into());
468 self
469 }
470
471 #[must_use]
473 pub const fn is_retryable(&self) -> bool {
474 matches!(
475 self.kind,
476 ErrorKind::Timeout
477 | ErrorKind::Unavailable
478 | ErrorKind::Transport
479 | ErrorKind::ExternalService
480 | ErrorKind::RateLimited
481 )
482 }
483
484 #[must_use]
486 pub const fn is_temporary(&self) -> bool {
487 matches!(
488 self.kind,
489 ErrorKind::Timeout
490 | ErrorKind::Unavailable
491 | ErrorKind::RateLimited
492 | ErrorKind::ExternalService
493 | ErrorKind::ServerOverloaded
494 )
495 }
496
497 #[must_use]
499 pub const fn jsonrpc_code(&self) -> i32 {
500 self.jsonrpc_error_code()
501 }
502
503 #[must_use]
505 pub const fn jsonrpc_error_code(&self) -> i32 {
506 match self.kind {
507 ErrorKind::ParseError => -32700,
509 ErrorKind::InvalidRequest => -32600,
510 ErrorKind::MethodNotFound => -32601,
511 ErrorKind::InvalidParams => -32602,
512 ErrorKind::Internal | ErrorKind::Serialization => -32603,
515 ErrorKind::UserRejected => -1,
517 ErrorKind::ToolNotFound => -32001,
518 ErrorKind::ToolExecutionFailed => -32002,
519 ErrorKind::PromptNotFound => -32003,
520 ErrorKind::ResourceNotFound => -32004,
521 ErrorKind::ResourceAccessDenied => -32005,
522 ErrorKind::CapabilityNotSupported => -32006,
523 ErrorKind::ProtocolVersionMismatch => -32007,
524 ErrorKind::UrlElicitationRequired => -32042,
525 ErrorKind::Authentication => -32008,
526 ErrorKind::RateLimited => -32009,
527 ErrorKind::ServerOverloaded => -32010,
528 ErrorKind::PermissionDenied => -32011,
530 ErrorKind::Timeout => -32012,
531 ErrorKind::Unavailable => -32013,
532 ErrorKind::Transport => -32014,
533 ErrorKind::Configuration => -32015,
534 ErrorKind::ExternalService => -32016,
535 ErrorKind::Cancelled => -32017,
536 ErrorKind::Security => -32018,
537 }
538 }
539
540 #[must_use]
542 pub const fn http_status(&self) -> u16 {
543 match self.kind {
544 ErrorKind::InvalidParams
546 | ErrorKind::InvalidRequest
547 | ErrorKind::UserRejected
548 | ErrorKind::ParseError => 400,
549 ErrorKind::Authentication => 401,
550 ErrorKind::PermissionDenied | ErrorKind::Security | ErrorKind::ResourceAccessDenied => {
551 403
552 }
553 ErrorKind::ToolNotFound
554 | ErrorKind::PromptNotFound
555 | ErrorKind::ResourceNotFound
556 | ErrorKind::MethodNotFound => 404,
557 ErrorKind::UrlElicitationRequired => 403,
559 ErrorKind::Timeout => 408,
560 ErrorKind::RateLimited => 429,
561 ErrorKind::Cancelled => 499,
562 ErrorKind::Internal
564 | ErrorKind::Configuration
565 | ErrorKind::Serialization
566 | ErrorKind::ToolExecutionFailed
567 | ErrorKind::CapabilityNotSupported
568 | ErrorKind::ProtocolVersionMismatch => 500,
569 ErrorKind::Transport
570 | ErrorKind::ExternalService
571 | ErrorKind::Unavailable
572 | ErrorKind::ServerOverloaded => 503,
573 }
574 }
575}
576
577impl ErrorKind {
578 #[must_use]
582 pub fn from_i32(code: i32) -> Self {
583 match code {
584 -1 => Self::UserRejected,
586 -32001 => Self::ToolNotFound,
587 -32002 => Self::ToolExecutionFailed,
588 -32003 => Self::PromptNotFound,
589 -32004 => Self::ResourceNotFound,
590 -32005 => Self::ResourceAccessDenied,
591 -32006 => Self::CapabilityNotSupported,
592 -32007 => Self::ProtocolVersionMismatch,
593 -32008 => Self::Authentication,
594 -32009 => Self::RateLimited,
595 -32010 => Self::ServerOverloaded,
596 -32042 => Self::UrlElicitationRequired,
598 -32600 => Self::InvalidRequest,
600 -32601 => Self::MethodNotFound,
601 -32602 => Self::InvalidParams,
602 -32603 => Self::Internal,
603 -32700 => Self::ParseError,
604 _ => Self::Internal,
605 }
606 }
607
608 #[must_use]
610 pub const fn description(self) -> &'static str {
611 match self {
612 Self::ToolNotFound => "Tool not found",
613 Self::ToolExecutionFailed => "Tool execution failed",
614 Self::PromptNotFound => "Prompt not found",
615 Self::ResourceNotFound => "Resource not found",
616 Self::ResourceAccessDenied => "Resource access denied",
617 Self::CapabilityNotSupported => "Capability not supported",
618 Self::ProtocolVersionMismatch => "Protocol version mismatch",
619 Self::UrlElicitationRequired => "URL elicitation required",
620 Self::UserRejected => "User rejected request",
621 Self::ParseError => "Parse error",
622 Self::InvalidRequest => "Invalid request",
623 Self::MethodNotFound => "Method not found",
624 Self::InvalidParams => "Invalid parameters",
625 Self::Internal => "Internal error",
626 Self::Authentication => "Authentication failed",
627 Self::PermissionDenied => "Permission denied",
628 Self::Transport => "Transport error",
629 Self::Timeout => "Operation timed out",
630 Self::Unavailable => "Service unavailable",
631 Self::RateLimited => "Rate limit exceeded",
632 Self::ServerOverloaded => "Server overloaded",
633 Self::Configuration => "Configuration error",
634 Self::ExternalService => "External service error",
635 Self::Cancelled => "Operation cancelled",
636 Self::Security => "Security violation",
637 Self::Serialization => "Serialization error",
638 }
639 }
640}
641
642impl fmt::Display for McpError {
643 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644 write!(f, "{}", self.message)?;
645 if let Some(ctx) = &self.context {
646 if let Some(op) = &ctx.operation {
647 write!(f, " (operation: {})", op)?;
648 }
649 if let Some(comp) = &ctx.component {
650 write!(f, " (component: {})", comp)?;
651 }
652 }
653 Ok(())
654 }
655}
656
657impl fmt::Display for ErrorKind {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 write!(f, "{}", self.description())
660 }
661}
662
663#[cfg(feature = "std")]
664impl std::error::Error for McpError {}
665
666impl From<Box<McpError>> for McpError {
671 fn from(boxed: Box<McpError>) -> Self {
672 *boxed
673 }
674}
675
676impl From<serde_json::Error> for McpError {
677 fn from(err: serde_json::Error) -> Self {
678 let kind = if err.is_syntax() || err.is_eof() {
680 ErrorKind::ParseError
681 } else if err.is_data() {
682 ErrorKind::InvalidParams
683 } else {
684 ErrorKind::Serialization
685 };
686 Self::new(kind, alloc::format!("JSON error: {}", err))
687 }
688}
689
690#[cfg(feature = "std")]
691impl From<std::io::Error> for McpError {
692 fn from(err: std::io::Error) -> Self {
693 use std::io::ErrorKind as IoKind;
694 let kind = match err.kind() {
695 IoKind::NotFound => ErrorKind::ResourceNotFound,
696 IoKind::PermissionDenied => ErrorKind::PermissionDenied,
697 IoKind::ConnectionRefused
698 | IoKind::ConnectionReset
699 | IoKind::ConnectionAborted
700 | IoKind::NotConnected
701 | IoKind::BrokenPipe => ErrorKind::Transport,
702 IoKind::TimedOut => ErrorKind::Timeout,
703 _ => ErrorKind::Internal,
704 };
705 Self::new(kind, alloc::format!("IO error: {}", err))
706 }
707}
708
709#[macro_export]
711macro_rules! mcp_err {
712 ($kind:expr, $msg:expr) => {
713 $crate::error::McpError::new($kind, $msg)
714 .with_source_location(concat!(file!(), ":", line!()))
715 };
716 ($kind:expr, $fmt:expr, $($arg:tt)*) => {
717 $crate::error::McpError::new($kind, alloc::format!($fmt, $($arg)*))
718 .with_source_location(concat!(file!(), ":", line!()))
719 };
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use alloc::string::ToString;
726
727 #[test]
728 fn test_error_creation() {
729 let err = McpError::invalid_params("missing field");
730 assert_eq!(err.kind, ErrorKind::InvalidParams);
731 assert!(err.message.contains("missing field"));
732 }
733
734 #[test]
735 fn test_error_context() {
736 let err = McpError::internal("test")
737 .with_operation("test_op")
738 .with_component("test_comp")
739 .with_request_id("req-123");
740
741 let ctx = err.context.unwrap();
742 assert_eq!(ctx.operation, Some("test_op".to_string()));
743 assert_eq!(ctx.component, Some("test_comp".to_string()));
744 assert_eq!(ctx.request_id, Some("req-123".to_string()));
745 }
746
747 #[test]
748 fn test_jsonrpc_codes() {
749 assert_eq!(McpError::tool_not_found("x").jsonrpc_code(), -32001);
750 assert_eq!(McpError::invalid_params("x").jsonrpc_code(), -32602);
751 assert_eq!(McpError::internal("x").jsonrpc_code(), -32603);
752 }
753
754 #[test]
755 fn test_retryable() {
756 assert!(McpError::timeout("x").is_retryable());
757 assert!(McpError::rate_limited("x").is_retryable());
758 assert!(!McpError::invalid_params("x").is_retryable());
759 }
760
761 #[test]
762 fn test_http_status() {
763 assert_eq!(McpError::tool_not_found("x").http_status(), 404);
764 assert_eq!(McpError::authentication("x").http_status(), 401);
765 assert_eq!(McpError::internal("x").http_status(), 500);
766 }
767
768 #[test]
769 fn test_error_size_reasonable() {
770 assert!(
772 core::mem::size_of::<McpError>() <= 128,
773 "McpError size: {} bytes (should be ≤128)",
774 core::mem::size_of::<McpError>()
775 );
776 }
777
778 #[test]
780 fn test_error_kind_from_i32() {
781 assert_eq!(ErrorKind::from_i32(-32001), ErrorKind::ToolNotFound);
783 assert_eq!(ErrorKind::from_i32(-32002), ErrorKind::ToolExecutionFailed);
784 assert_eq!(ErrorKind::from_i32(-32003), ErrorKind::PromptNotFound);
785 assert_eq!(ErrorKind::from_i32(-32004), ErrorKind::ResourceNotFound);
786 assert_eq!(ErrorKind::from_i32(-32005), ErrorKind::ResourceAccessDenied);
787 assert_eq!(
788 ErrorKind::from_i32(-32006),
789 ErrorKind::CapabilityNotSupported
790 );
791 assert_eq!(
792 ErrorKind::from_i32(-32007),
793 ErrorKind::ProtocolVersionMismatch
794 );
795 assert_eq!(ErrorKind::from_i32(-32008), ErrorKind::Authentication);
796 assert_eq!(ErrorKind::from_i32(-32009), ErrorKind::RateLimited);
797 assert_eq!(ErrorKind::from_i32(-32010), ErrorKind::ServerOverloaded);
798 assert_eq!(
800 ErrorKind::from_i32(-32042),
801 ErrorKind::UrlElicitationRequired
802 );
803 assert_eq!(ErrorKind::from_i32(-32600), ErrorKind::InvalidRequest);
805 assert_eq!(ErrorKind::from_i32(-32601), ErrorKind::MethodNotFound);
806 assert_eq!(ErrorKind::from_i32(-32602), ErrorKind::InvalidParams);
807 assert_eq!(ErrorKind::from_i32(-32603), ErrorKind::Internal);
808 assert_eq!(ErrorKind::from_i32(-32700), ErrorKind::ParseError);
809 assert_eq!(ErrorKind::from_i32(-99999), ErrorKind::Internal);
811 assert_eq!(ErrorKind::from_i32(0), ErrorKind::Internal);
812 }
813}