1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(into = "i32", from = "i32")]
13pub enum McpErrorCode {
14 ParseError,
16 InvalidRequest,
18 MethodNotFound,
20 InvalidParams,
22 InternalError,
24 ToolExecutionError,
26 ResourceNotFound,
28 ResourceForbidden,
30 PromptNotFound,
32 RequestCancelled,
34 Custom(i32),
36}
37
38impl From<McpErrorCode> for i32 {
39 fn from(code: McpErrorCode) -> Self {
40 match code {
41 McpErrorCode::ParseError => -32700,
42 McpErrorCode::InvalidRequest => -32600,
43 McpErrorCode::MethodNotFound => -32601,
44 McpErrorCode::InvalidParams => -32602,
45 McpErrorCode::InternalError => -32603,
46 McpErrorCode::ToolExecutionError => -32000,
48 McpErrorCode::ResourceNotFound => -32001,
49 McpErrorCode::ResourceForbidden => -32002,
50 McpErrorCode::PromptNotFound => -32003,
51 McpErrorCode::RequestCancelled => -32004,
52 McpErrorCode::Custom(code) => code,
53 }
54 }
55}
56
57impl From<i32> for McpErrorCode {
58 fn from(code: i32) -> Self {
59 match code {
60 -32700 => McpErrorCode::ParseError,
61 -32600 => McpErrorCode::InvalidRequest,
62 -32601 => McpErrorCode::MethodNotFound,
63 -32602 => McpErrorCode::InvalidParams,
64 -32603 => McpErrorCode::InternalError,
65 -32000 => McpErrorCode::ToolExecutionError,
66 -32001 => McpErrorCode::ResourceNotFound,
67 -32002 => McpErrorCode::ResourceForbidden,
68 -32003 => McpErrorCode::PromptNotFound,
69 -32004 => McpErrorCode::RequestCancelled,
70 code => McpErrorCode::Custom(code),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct McpError {
81 pub code: McpErrorCode,
83 pub message: String,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub data: Option<serde_json::Value>,
88}
89
90impl McpError {
91 #[must_use]
93 pub fn new(code: McpErrorCode, message: impl Into<String>) -> Self {
94 Self {
95 code,
96 message: message.into(),
97 data: None,
98 }
99 }
100
101 #[must_use]
103 pub fn with_data(
104 code: McpErrorCode,
105 message: impl Into<String>,
106 data: serde_json::Value,
107 ) -> Self {
108 Self {
109 code,
110 message: message.into(),
111 data: Some(data),
112 }
113 }
114
115 #[must_use]
117 pub fn parse_error(message: impl Into<String>) -> Self {
118 Self::new(McpErrorCode::ParseError, message)
119 }
120
121 #[must_use]
123 pub fn invalid_request(message: impl Into<String>) -> Self {
124 Self::new(McpErrorCode::InvalidRequest, message)
125 }
126
127 #[must_use]
129 pub fn method_not_found(method: &str) -> Self {
130 Self::new(
131 McpErrorCode::MethodNotFound,
132 format!("Method not found: {method}"),
133 )
134 }
135
136 #[must_use]
138 pub fn invalid_params(message: impl Into<String>) -> Self {
139 Self::new(McpErrorCode::InvalidParams, message)
140 }
141
142 #[must_use]
144 pub fn internal_error(message: impl Into<String>) -> Self {
145 Self::new(McpErrorCode::InternalError, message)
146 }
147
148 #[must_use]
150 pub fn tool_error(message: impl Into<String>) -> Self {
151 Self::new(McpErrorCode::ToolExecutionError, message)
152 }
153
154 #[must_use]
156 pub fn resource_not_found(uri: &str) -> Self {
157 Self::new(
158 McpErrorCode::ResourceNotFound,
159 format!("Resource not found: {uri}"),
160 )
161 }
162
163 #[must_use]
165 pub fn request_cancelled() -> Self {
166 Self::new(McpErrorCode::RequestCancelled, "Request was cancelled")
167 }
168
169 #[must_use]
202 pub fn masked(&self, mask_enabled: bool) -> McpError {
203 if !mask_enabled {
204 return self.clone();
205 }
206
207 match self.code {
208 McpErrorCode::ParseError
210 | McpErrorCode::InvalidRequest
211 | McpErrorCode::MethodNotFound
212 | McpErrorCode::InvalidParams
213 | McpErrorCode::ResourceNotFound
214 | McpErrorCode::ResourceForbidden
215 | McpErrorCode::PromptNotFound
216 | McpErrorCode::RequestCancelled => self.clone(),
217
218 McpErrorCode::InternalError
220 | McpErrorCode::ToolExecutionError
221 | McpErrorCode::Custom(_) => McpError {
222 code: self.code,
223 message: "Internal server error".to_string(),
224 data: None,
225 },
226 }
227 }
228
229 #[must_use]
231 pub fn is_internal(&self) -> bool {
232 matches!(
233 self.code,
234 McpErrorCode::InternalError
235 | McpErrorCode::ToolExecutionError
236 | McpErrorCode::Custom(_)
237 )
238 }
239}
240
241impl std::fmt::Display for McpError {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 write!(f, "[{}] {}", i32::from(self.code), self.message)
244 }
245}
246
247impl std::error::Error for McpError {}
248
249impl Default for McpError {
250 fn default() -> Self {
251 Self::internal_error("Unknown error")
252 }
253}
254
255impl From<crate::CancelledError> for McpError {
256 fn from(_: crate::CancelledError) -> Self {
257 Self::request_cancelled()
258 }
259}
260
261impl From<serde_json::Error> for McpError {
262 fn from(err: serde_json::Error) -> Self {
263 Self::parse_error(err.to_string())
264 }
265}
266
267pub type McpResult<T> = Result<T, McpError>;
269
270pub type McpOutcome<T> = Outcome<T, McpError>;
284
285use asupersync::Outcome;
288use asupersync::types::CancelReason;
289
290pub trait OutcomeExt<T> {
292 fn into_mcp_result(self) -> McpResult<T>;
294
295 fn map_ok<U>(self, f: impl FnOnce(T) -> U) -> Outcome<U, McpError>;
297}
298
299impl<T> OutcomeExt<T> for Outcome<T, McpError> {
300 fn into_mcp_result(self) -> McpResult<T> {
301 match self {
302 Outcome::Ok(v) => Ok(v),
303 Outcome::Err(e) => Err(e),
304 Outcome::Cancelled(_) => Err(McpError::request_cancelled()),
305 Outcome::Panicked(payload) => Err(McpError::internal_error(format!(
306 "Internal panic: {}",
307 payload.message()
308 ))),
309 }
310 }
311
312 fn map_ok<U>(self, f: impl FnOnce(T) -> U) -> Outcome<U, McpError> {
313 match self {
314 Outcome::Ok(v) => Outcome::Ok(f(v)),
315 Outcome::Err(e) => Outcome::Err(e),
316 Outcome::Cancelled(r) => Outcome::Cancelled(r),
317 Outcome::Panicked(p) => Outcome::Panicked(p),
318 }
319 }
320}
321
322pub trait ResultExt<T, E> {
324 fn into_outcome(self) -> Outcome<T, E>;
326}
327
328impl<T, E> ResultExt<T, E> for Result<T, E> {
329 fn into_outcome(self) -> Outcome<T, E> {
330 match self {
331 Ok(v) => Outcome::Ok(v),
332 Err(e) => Outcome::Err(e),
333 }
334 }
335}
336
337impl<T> ResultExt<T, McpError> for Result<T, crate::CancelledError> {
338 fn into_outcome(self) -> Outcome<T, McpError> {
339 match self {
340 Ok(v) => Outcome::Ok(v),
341 Err(_) => Outcome::Cancelled(CancelReason::user("request cancelled")),
342 }
343 }
344}
345
346#[must_use]
347pub fn cancelled<T>() -> Outcome<T, McpError> {
349 Outcome::Cancelled(CancelReason::user("request cancelled"))
350}
351
352#[must_use]
353pub fn err<T>(error: McpError) -> Outcome<T, McpError> {
355 Outcome::Err(error)
356}
357
358#[must_use]
359pub fn ok<T>(value: T) -> Outcome<T, McpError> {
361 Outcome::Ok(value)
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use asupersync::types::PanicPayload;
368
369 #[test]
374 fn test_error_code_serialization() {
375 let code = McpErrorCode::MethodNotFound;
376 let value: i32 = code.into();
377 assert_eq!(value, -32601);
378 }
379
380 #[test]
381 fn test_all_standard_error_codes() {
382 assert_eq!(i32::from(McpErrorCode::ParseError), -32700);
383 assert_eq!(i32::from(McpErrorCode::InvalidRequest), -32600);
384 assert_eq!(i32::from(McpErrorCode::MethodNotFound), -32601);
385 assert_eq!(i32::from(McpErrorCode::InvalidParams), -32602);
386 assert_eq!(i32::from(McpErrorCode::InternalError), -32603);
387 assert_eq!(i32::from(McpErrorCode::ToolExecutionError), -32000);
388 assert_eq!(i32::from(McpErrorCode::ResourceNotFound), -32001);
389 assert_eq!(i32::from(McpErrorCode::ResourceForbidden), -32002);
390 assert_eq!(i32::from(McpErrorCode::PromptNotFound), -32003);
391 assert_eq!(i32::from(McpErrorCode::RequestCancelled), -32004);
392 }
393
394 #[test]
395 fn test_error_code_roundtrip() {
396 let codes = vec![
397 McpErrorCode::ParseError,
398 McpErrorCode::InvalidRequest,
399 McpErrorCode::MethodNotFound,
400 McpErrorCode::InvalidParams,
401 McpErrorCode::InternalError,
402 McpErrorCode::ToolExecutionError,
403 McpErrorCode::ResourceNotFound,
404 McpErrorCode::ResourceForbidden,
405 McpErrorCode::PromptNotFound,
406 McpErrorCode::RequestCancelled,
407 ];
408
409 for code in codes {
410 let value: i32 = code.into();
411 let roundtrip: McpErrorCode = value.into();
412 assert_eq!(code, roundtrip);
413 }
414 }
415
416 #[test]
417 fn test_custom_error_code() {
418 let custom = McpErrorCode::Custom(-99999);
419 let value: i32 = custom.into();
420 assert_eq!(value, -99999);
421
422 let from_int: McpErrorCode = (-99999).into();
423 assert!(matches!(from_int, McpErrorCode::Custom(-99999)));
424 }
425
426 #[test]
431 fn test_error_display() {
432 let err = McpError::method_not_found("tools/call");
433 assert!(err.to_string().contains("-32601"));
434 assert!(err.to_string().contains("tools/call"));
435 }
436
437 #[test]
438 fn test_error_factory_methods() {
439 let parse = McpError::parse_error("invalid json");
440 assert_eq!(parse.code, McpErrorCode::ParseError);
441
442 let invalid_req = McpError::invalid_request("bad request");
443 assert_eq!(invalid_req.code, McpErrorCode::InvalidRequest);
444
445 let method = McpError::method_not_found("foo/bar");
446 assert_eq!(method.code, McpErrorCode::MethodNotFound);
447
448 let params = McpError::invalid_params("missing field");
449 assert_eq!(params.code, McpErrorCode::InvalidParams);
450
451 let internal = McpError::internal_error("panic");
452 assert_eq!(internal.code, McpErrorCode::InternalError);
453
454 let tool = McpError::tool_error("execution failed");
455 assert_eq!(tool.code, McpErrorCode::ToolExecutionError);
456
457 let resource = McpError::resource_not_found("file://test");
458 assert_eq!(resource.code, McpErrorCode::ResourceNotFound);
459
460 let cancelled = McpError::request_cancelled();
461 assert_eq!(cancelled.code, McpErrorCode::RequestCancelled);
462 }
463
464 #[test]
465 fn test_error_with_data() {
466 let data = serde_json::json!({"details": "more info"});
467 let err = McpError::with_data(McpErrorCode::InternalError, "error", data.clone());
468
469 assert_eq!(err.code, McpErrorCode::InternalError);
470 assert_eq!(err.message, "error");
471 assert_eq!(err.data, Some(data));
472 }
473
474 #[test]
475 fn test_error_default() {
476 let err = McpError::default();
477 assert_eq!(err.code, McpErrorCode::InternalError);
478 }
479
480 #[test]
481 fn test_error_from_cancelled() {
482 let cancelled_err = crate::CancelledError;
483 let mcp_err: McpError = cancelled_err.into();
484 assert_eq!(mcp_err.code, McpErrorCode::RequestCancelled);
485 }
486
487 #[test]
488 fn test_error_serialization() {
489 let err = McpError::method_not_found("test");
490 let json = serde_json::to_string(&err).unwrap();
491 assert!(json.contains("-32601"));
492 assert!(json.contains("Method not found: test"));
493 }
494
495 #[test]
500 fn test_outcome_into_mcp_result_ok() {
501 let outcome: Outcome<i32, McpError> = Outcome::Ok(42);
502 let result = outcome.into_mcp_result();
503 assert!(matches!(result, Ok(42)));
504 }
505
506 #[test]
507 fn test_outcome_into_mcp_result_err() {
508 let outcome: Outcome<i32, McpError> = Outcome::Err(McpError::internal_error("test"));
509 let result = outcome.into_mcp_result();
510 assert!(result.is_err());
511 }
512
513 #[test]
514 fn test_outcome_into_mcp_result_cancelled() {
515 let outcome: Outcome<i32, McpError> = Outcome::Cancelled(CancelReason::user("user cancel"));
516 let result = outcome.into_mcp_result();
517 assert!(result.is_err());
518 assert_eq!(result.unwrap_err().code, McpErrorCode::RequestCancelled);
519 }
520
521 #[test]
522 fn test_outcome_into_mcp_result_panicked() {
523 let outcome: Outcome<i32, McpError> = Outcome::Panicked(PanicPayload::new("test panic"));
524 let result = outcome.into_mcp_result();
525 assert!(result.is_err());
526 assert_eq!(result.unwrap_err().code, McpErrorCode::InternalError);
527 }
528
529 #[test]
530 fn test_outcome_map_ok() {
531 let outcome: Outcome<i32, McpError> = Outcome::Ok(21);
532 let mapped = outcome.map_ok(|x| x * 2);
533 assert!(matches!(mapped, Outcome::Ok(42)));
534 }
535
536 #[test]
537 fn test_result_ext_into_outcome() {
538 let result: Result<i32, McpError> = Ok(42);
539 let outcome = result.into_outcome();
540 assert!(matches!(outcome, Outcome::Ok(42)));
541
542 let err_result: Result<i32, McpError> = Err(McpError::internal_error("test"));
543 let outcome = err_result.into_outcome();
544 assert!(matches!(outcome, Outcome::Err(_)));
545 }
546
547 #[test]
552 fn test_helper_ok() {
553 let outcome: Outcome<i32, McpError> = ok(42);
554 assert!(matches!(outcome, Outcome::Ok(42)));
555 }
556
557 #[test]
558 fn test_helper_err() {
559 let outcome: Outcome<i32, McpError> = err(McpError::internal_error("test"));
560 assert!(matches!(outcome, Outcome::Err(_)));
561 }
562
563 #[test]
564 fn test_helper_cancelled() {
565 let outcome: Outcome<i32, McpError> = cancelled();
566 assert!(matches!(outcome, Outcome::Cancelled(_)));
567 }
568
569 #[test]
574 fn test_masked_preserves_client_errors() {
575 let parse = McpError::parse_error("invalid json");
577 let masked = parse.masked(true);
578 assert_eq!(masked.message, "invalid json");
579
580 let invalid = McpError::invalid_request("bad request");
581 let masked = invalid.masked(true);
582 assert!(masked.message.contains("bad request"));
583
584 let method = McpError::method_not_found("unknown");
585 let masked = method.masked(true);
586 assert!(masked.message.contains("unknown"));
587
588 let params = McpError::invalid_params("missing field");
589 let masked = params.masked(true);
590 assert!(masked.message.contains("missing field"));
591
592 let resource = McpError::resource_not_found("file://test");
593 let masked = resource.masked(true);
594 assert!(masked.message.contains("file://test"));
595
596 let cancelled = McpError::request_cancelled();
597 let masked = cancelled.masked(true);
598 assert!(masked.message.contains("cancelled"));
599 }
600
601 #[test]
602 fn test_masked_hides_internal_errors() {
603 let internal = McpError::internal_error("Connection failed at /etc/secrets/db.conf");
605 let masked = internal.masked(true);
606 assert_eq!(masked.message, "Internal server error");
607 assert!(masked.data.is_none());
608 assert_eq!(masked.code, McpErrorCode::InternalError);
609
610 let tool = McpError::tool_error("Failed: /home/user/secret.txt");
611 let masked = tool.masked(true);
612 assert_eq!(masked.message, "Internal server error");
613 assert!(masked.data.is_none());
614
615 let custom = McpError::new(McpErrorCode::Custom(-99999), "Stack trace: ...");
616 let masked = custom.masked(true);
617 assert_eq!(masked.message, "Internal server error");
618 }
619
620 #[test]
621 fn test_masked_with_data_removed() {
622 let data = serde_json::json!({"internal": "secret", "path": "/etc/passwd"});
623 let internal = McpError::with_data(McpErrorCode::InternalError, "Failure", data);
624
625 let masked = internal.masked(true);
627 assert_eq!(masked.message, "Internal server error");
628 assert!(masked.data.is_none());
629
630 let unmasked = internal.masked(false);
632 assert_eq!(unmasked.message, "Failure");
633 assert!(unmasked.data.is_some());
634 }
635
636 #[test]
637 fn test_masked_disabled() {
638 let internal = McpError::internal_error("Full details here");
640 let masked = internal.masked(false);
641 assert_eq!(masked.message, "Full details here");
642 }
643
644 #[test]
645 fn test_is_internal() {
646 assert!(McpError::internal_error("test").is_internal());
647 assert!(McpError::tool_error("test").is_internal());
648 assert!(McpError::new(McpErrorCode::Custom(-99999), "test").is_internal());
649
650 assert!(!McpError::parse_error("test").is_internal());
651 assert!(!McpError::invalid_request("test").is_internal());
652 assert!(!McpError::method_not_found("test").is_internal());
653 assert!(!McpError::invalid_params("test").is_internal());
654 assert!(!McpError::resource_not_found("test").is_internal());
655 assert!(!McpError::request_cancelled().is_internal());
656 assert!(!McpError::new(McpErrorCode::ResourceForbidden, "forbidden").is_internal());
657 assert!(!McpError::new(McpErrorCode::PromptNotFound, "not found").is_internal());
658 }
659
660 #[test]
665 fn from_serde_json_error() {
666 let serde_err: serde_json::Error =
668 serde_json::from_str::<serde_json::Value>("{{bad json").unwrap_err();
669 let mcp_err: McpError = serde_err.into();
670 assert_eq!(mcp_err.code, McpErrorCode::ParseError);
671 assert!(!mcp_err.message.is_empty());
672 }
673
674 #[test]
675 fn map_ok_err_variant() {
676 let outcome: Outcome<i32, McpError> = Outcome::Err(McpError::internal_error("oops"));
677 let mapped = outcome.map_ok(|x| x * 2);
678 match mapped {
679 Outcome::Err(e) => assert_eq!(e.code, McpErrorCode::InternalError),
680 other => panic!("expected Err, got {other:?}"),
681 }
682 }
683
684 #[test]
685 fn map_ok_cancelled_variant() {
686 let outcome: Outcome<i32, McpError> = Outcome::Cancelled(CancelReason::user("test cancel"));
687 let mapped = outcome.map_ok(|x| x * 2);
688 assert!(matches!(mapped, Outcome::Cancelled(_)));
689 }
690
691 #[test]
692 fn map_ok_panicked_variant() {
693 let outcome: Outcome<i32, McpError> = Outcome::Panicked(PanicPayload::new("boom"));
694 let mapped = outcome.map_ok(|x| x * 2);
695 assert!(matches!(mapped, Outcome::Panicked(_)));
696 }
697
698 #[test]
699 fn result_ext_cancelled_error_ok() {
700 let result: Result<i32, crate::CancelledError> = Ok(42);
701 let outcome: Outcome<i32, McpError> = result.into_outcome();
702 assert!(matches!(outcome, Outcome::Ok(42)));
703 }
704
705 #[test]
706 fn result_ext_cancelled_error_err() {
707 let result: Result<i32, crate::CancelledError> = Err(crate::CancelledError);
708 let outcome: Outcome<i32, McpError> = result.into_outcome();
709 assert!(matches!(outcome, Outcome::Cancelled(_)));
710 }
711
712 #[test]
713 fn error_code_json_serde_roundtrip() {
714 let codes = [
715 McpErrorCode::ParseError,
716 McpErrorCode::InvalidRequest,
717 McpErrorCode::MethodNotFound,
718 McpErrorCode::InvalidParams,
719 McpErrorCode::InternalError,
720 McpErrorCode::ToolExecutionError,
721 McpErrorCode::ResourceNotFound,
722 McpErrorCode::ResourceForbidden,
723 McpErrorCode::PromptNotFound,
724 McpErrorCode::RequestCancelled,
725 McpErrorCode::Custom(-12345),
726 ];
727 for code in codes {
728 let json = serde_json::to_string(&code).unwrap();
729 let deserialized: McpErrorCode = serde_json::from_str(&json).unwrap();
730 assert_eq!(code, deserialized, "roundtrip failed for {code:?}");
731 }
732 }
733
734 #[test]
735 fn mcp_error_json_deserialization() {
736 let json = r#"{"code":-32601,"message":"Method not found: test","data":{"key":"val"}}"#;
737 let err: McpError = serde_json::from_str(json).unwrap();
738 assert_eq!(err.code, McpErrorCode::MethodNotFound);
739 assert!(err.message.contains("test"));
740 assert!(err.data.is_some());
741 assert_eq!(err.data.unwrap()["key"], "val");
742 }
743
744 #[test]
745 fn mcp_error_json_deserialization_no_data() {
746 let json = r#"{"code":-32603,"message":"Internal error"}"#;
747 let err: McpError = serde_json::from_str(json).unwrap();
748 assert_eq!(err.code, McpErrorCode::InternalError);
749 assert!(err.data.is_none());
750 }
751
752 #[test]
753 fn masked_preserves_resource_forbidden() {
754 let err = McpError::new(McpErrorCode::ResourceForbidden, "access denied");
755 let masked = err.masked(true);
756 assert_eq!(masked.message, "access denied");
757 assert_eq!(masked.code, McpErrorCode::ResourceForbidden);
758 }
759
760 #[test]
761 fn masked_preserves_prompt_not_found() {
762 let err = McpError::new(McpErrorCode::PromptNotFound, "no such prompt");
763 let masked = err.masked(true);
764 assert_eq!(masked.message, "no such prompt");
765 assert_eq!(masked.code, McpErrorCode::PromptNotFound);
766 }
767
768 #[test]
769 fn mcp_error_is_std_error() {
770 let err = McpError::internal_error("test");
771 let _: &dyn std::error::Error = &err;
772 }
773
774 #[test]
775 fn mcp_error_debug_and_clone() {
776 let err = McpError::with_data(
777 McpErrorCode::ToolExecutionError,
778 "fail",
779 serde_json::json!({"x": 1}),
780 );
781 let debug = format!("{err:?}");
782 assert!(debug.contains("McpError"));
783 assert!(debug.contains("fail"));
784 let cloned = err.clone();
785 assert_eq!(cloned.code, err.code);
786 assert_eq!(cloned.message, err.message);
787 assert_eq!(cloned.data, err.data);
788 }
789
790 #[test]
791 fn error_code_debug_clone_copy() {
792 let code = McpErrorCode::ResourceForbidden;
793 let debug = format!("{code:?}");
794 assert!(debug.contains("ResourceForbidden"));
795 let cloned = code;
796 assert_eq!(code, cloned);
797 }
798}