1use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum McpErrorCode {
13 ParseError = -32700,
16 InvalidRequest = -32600,
18 MethodNotFound = -32601,
20 InvalidParams = -32602,
22 InternalError = -32603,
24
25 ConnectionError = -32000,
28 TransportError = -32001,
30 ProtocolError = -32002,
32 TimeoutError = -32003,
34 CancelledError = -32004,
36 ValidationError = -32005,
38 ConfigError = -32006,
40 LifecycleError = -32007,
42 ToolError = -32008,
44 ResourceError = -32009,
46 PermissionDenied = -32010,
48}
49
50impl McpErrorCode {
51 pub fn code(&self) -> i32 {
53 *self as i32
54 }
55
56 pub fn description(&self) -> &'static str {
58 match self {
59 Self::ParseError => "Parse error",
60 Self::InvalidRequest => "Invalid request",
61 Self::MethodNotFound => "Method not found",
62 Self::InvalidParams => "Invalid params",
63 Self::InternalError => "Internal error",
64 Self::ConnectionError => "Connection error",
65 Self::TransportError => "Transport error",
66 Self::ProtocolError => "Protocol error",
67 Self::TimeoutError => "Timeout error",
68 Self::CancelledError => "Cancelled",
69 Self::ValidationError => "Validation error",
70 Self::ConfigError => "Configuration error",
71 Self::LifecycleError => "Lifecycle error",
72 Self::ToolError => "Tool error",
73 Self::ResourceError => "Resource error",
74 Self::PermissionDenied => "Permission denied",
75 }
76 }
77}
78
79#[derive(Debug, Error)]
89pub enum McpError {
90 #[error("Connection error: {message}")]
92 Connection {
93 code: i32,
95 message: String,
97 #[source]
99 source: Option<Box<dyn std::error::Error + Send + Sync>>,
100 },
101
102 #[error("Transport error: {message}")]
104 Transport {
105 code: i32,
107 message: String,
109 #[source]
111 source: Option<Box<dyn std::error::Error + Send + Sync>>,
112 },
113
114 #[error("Protocol error: {message}")]
116 Protocol {
117 code: i32,
119 message: String,
121 },
122
123 #[error("Timeout after {duration:?}: {message}")]
125 Timeout {
126 code: i32,
128 message: String,
130 duration: Duration,
132 },
133
134 #[error("Cancelled: {message}")]
136 Cancelled {
137 code: i32,
139 message: String,
141 reason: Option<String>,
143 },
144
145 #[error("Server error: code={code}, message={message}")]
147 Server {
148 code: i32,
150 message: String,
152 data: Option<serde_json::Value>,
154 },
155
156 #[error("Validation error: {message}")]
158 Validation {
159 code: i32,
161 message: String,
163 errors: Vec<String>,
165 },
166
167 #[error("Configuration error: {message}")]
169 Config {
170 code: i32,
172 message: String,
174 #[source]
176 source: Option<Box<dyn std::error::Error + Send + Sync>>,
177 },
178
179 #[error("IO error: {message}")]
181 Io {
182 code: i32,
184 message: String,
186 #[source]
188 source: std::io::Error,
189 },
190
191 #[error("Serialization error: {message}")]
193 Serialization {
194 code: i32,
196 message: String,
198 #[source]
200 source: serde_json::Error,
201 },
202
203 #[error("Lifecycle error: {message}")]
205 Lifecycle {
206 code: i32,
208 message: String,
210 server_name: Option<String>,
212 },
213
214 #[error("Tool error: {message}")]
216 Tool {
217 code: i32,
219 message: String,
221 tool_name: Option<String>,
223 },
224
225 #[error("Permission denied: {message}")]
227 PermissionDenied {
228 code: i32,
230 message: String,
232 tool_name: Option<String>,
234 },
235}
236
237impl McpError {
238 pub fn code(&self) -> i32 {
240 match self {
241 Self::Connection { code, .. } => *code,
242 Self::Transport { code, .. } => *code,
243 Self::Protocol { code, .. } => *code,
244 Self::Timeout { code, .. } => *code,
245 Self::Cancelled { code, .. } => *code,
246 Self::Server { code, .. } => *code,
247 Self::Validation { code, .. } => *code,
248 Self::Config { code, .. } => *code,
249 Self::Io { code, .. } => *code,
250 Self::Serialization { code, .. } => *code,
251 Self::Lifecycle { code, .. } => *code,
252 Self::Tool { code, .. } => *code,
253 Self::PermissionDenied { code, .. } => *code,
254 }
255 }
256
257 pub fn message(&self) -> &str {
259 match self {
260 Self::Connection { message, .. } => message,
261 Self::Transport { message, .. } => message,
262 Self::Protocol { message, .. } => message,
263 Self::Timeout { message, .. } => message,
264 Self::Cancelled { message, .. } => message,
265 Self::Server { message, .. } => message,
266 Self::Validation { message, .. } => message,
267 Self::Config { message, .. } => message,
268 Self::Io { message, .. } => message,
269 Self::Serialization { message, .. } => message,
270 Self::Lifecycle { message, .. } => message,
271 Self::Tool { message, .. } => message,
272 Self::PermissionDenied { message, .. } => message,
273 }
274 }
275
276 pub fn connection(message: impl Into<String>) -> Self {
280 Self::Connection {
281 code: McpErrorCode::ConnectionError.code(),
282 message: message.into(),
283 source: None,
284 }
285 }
286
287 pub fn connection_with_source(
289 message: impl Into<String>,
290 source: impl std::error::Error + Send + Sync + 'static,
291 ) -> Self {
292 Self::Connection {
293 code: McpErrorCode::ConnectionError.code(),
294 message: message.into(),
295 source: Some(Box::new(source)),
296 }
297 }
298
299 pub fn transport(message: impl Into<String>) -> Self {
301 Self::Transport {
302 code: McpErrorCode::TransportError.code(),
303 message: message.into(),
304 source: None,
305 }
306 }
307
308 pub fn transport_with_source(
310 message: impl Into<String>,
311 source: impl std::error::Error + Send + Sync + 'static,
312 ) -> Self {
313 Self::Transport {
314 code: McpErrorCode::TransportError.code(),
315 message: message.into(),
316 source: Some(Box::new(source)),
317 }
318 }
319
320 pub fn protocol(message: impl Into<String>) -> Self {
322 Self::Protocol {
323 code: McpErrorCode::ProtocolError.code(),
324 message: message.into(),
325 }
326 }
327
328 pub fn timeout(message: impl Into<String>, duration: Duration) -> Self {
330 Self::Timeout {
331 code: McpErrorCode::TimeoutError.code(),
332 message: message.into(),
333 duration,
334 }
335 }
336
337 pub fn cancelled(message: impl Into<String>, reason: Option<String>) -> Self {
339 Self::Cancelled {
340 code: McpErrorCode::CancelledError.code(),
341 message: message.into(),
342 reason,
343 }
344 }
345
346 pub fn server(code: i32, message: impl Into<String>, data: Option<serde_json::Value>) -> Self {
348 Self::Server {
349 code,
350 message: message.into(),
351 data,
352 }
353 }
354
355 pub fn validation(message: impl Into<String>, errors: Vec<String>) -> Self {
357 Self::Validation {
358 code: McpErrorCode::ValidationError.code(),
359 message: message.into(),
360 errors,
361 }
362 }
363
364 pub fn config(message: impl Into<String>) -> Self {
366 Self::Config {
367 code: McpErrorCode::ConfigError.code(),
368 message: message.into(),
369 source: None,
370 }
371 }
372
373 pub fn config_with_source(
375 message: impl Into<String>,
376 source: impl std::error::Error + Send + Sync + 'static,
377 ) -> Self {
378 Self::Config {
379 code: McpErrorCode::ConfigError.code(),
380 message: message.into(),
381 source: Some(Box::new(source)),
382 }
383 }
384
385 pub fn lifecycle(message: impl Into<String>, server_name: Option<String>) -> Self {
387 Self::Lifecycle {
388 code: McpErrorCode::LifecycleError.code(),
389 message: message.into(),
390 server_name,
391 }
392 }
393
394 pub fn tool(message: impl Into<String>, tool_name: Option<String>) -> Self {
396 Self::Tool {
397 code: McpErrorCode::ToolError.code(),
398 message: message.into(),
399 tool_name,
400 }
401 }
402
403 pub fn permission_denied(message: impl Into<String>) -> Self {
405 Self::PermissionDenied {
406 code: McpErrorCode::PermissionDenied.code(),
407 message: message.into(),
408 tool_name: None,
409 }
410 }
411
412 pub fn permission_denied_for_tool(
414 message: impl Into<String>,
415 tool_name: impl Into<String>,
416 ) -> Self {
417 Self::PermissionDenied {
418 code: McpErrorCode::PermissionDenied.code(),
419 message: message.into(),
420 tool_name: Some(tool_name.into()),
421 }
422 }
423}
424
425impl From<std::io::Error> for McpError {
426 fn from(err: std::io::Error) -> Self {
427 Self::Io {
428 code: McpErrorCode::InternalError.code(),
429 message: err.to_string(),
430 source: err,
431 }
432 }
433}
434
435impl From<serde_json::Error> for McpError {
436 fn from(err: serde_json::Error) -> Self {
437 Self::Serialization {
438 code: McpErrorCode::ParseError.code(),
439 message: err.to_string(),
440 source: err,
441 }
442 }
443}
444
445impl From<rmcp::ErrorData> for McpError {
447 fn from(err: rmcp::ErrorData) -> Self {
448 Self::Server {
449 code: err.code.0,
450 message: err.message.to_string(),
451 data: err.data,
452 }
453 }
454}
455
456pub type McpResult<T> = Result<T, McpError>;
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct StructuredError {
465 pub code: i32,
467 pub message: String,
469 #[serde(skip_serializing_if = "Option::is_none")]
471 pub data: Option<serde_json::Value>,
472}
473
474impl StructuredError {
475 pub fn new(code: i32, message: impl Into<String>) -> Self {
477 Self {
478 code,
479 message: message.into(),
480 data: None,
481 }
482 }
483
484 pub fn with_data(code: i32, message: impl Into<String>, data: serde_json::Value) -> Self {
486 Self {
487 code,
488 message: message.into(),
489 data: Some(data),
490 }
491 }
492}
493
494impl From<&McpError> for StructuredError {
495 fn from(err: &McpError) -> Self {
496 let data = match err {
497 McpError::Validation { errors, .. } => Some(serde_json::json!({ "errors": errors })),
498 McpError::Server { data, .. } => data.clone(),
499 McpError::Timeout { duration, .. } => {
500 Some(serde_json::json!({ "duration_ms": duration.as_millis() }))
501 }
502 McpError::Cancelled { reason, .. } => {
503 reason.as_ref().map(|r| serde_json::json!({ "reason": r }))
504 }
505 McpError::Lifecycle { server_name, .. } => server_name
506 .as_ref()
507 .map(|n| serde_json::json!({ "server_name": n })),
508 McpError::Tool { tool_name, .. } => tool_name
509 .as_ref()
510 .map(|n| serde_json::json!({ "tool_name": n })),
511 McpError::PermissionDenied { tool_name, .. } => tool_name
512 .as_ref()
513 .map(|n| serde_json::json!({ "tool_name": n })),
514 _ => None,
515 };
516
517 Self {
518 code: err.code(),
519 message: err.message().to_string(),
520 data,
521 }
522 }
523}
524
525impl From<McpError> for StructuredError {
526 fn from(err: McpError) -> Self {
527 StructuredError::from(&err)
528 }
529}
530
531use serde::{Deserialize, Serialize};
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_error_code_values() {
539 assert_eq!(McpErrorCode::ParseError.code(), -32700);
540 assert_eq!(McpErrorCode::InvalidRequest.code(), -32600);
541 assert_eq!(McpErrorCode::ConnectionError.code(), -32000);
542 }
543
544 #[test]
545 fn test_error_has_code_and_message() {
546 let err = McpError::connection("test connection error");
547 assert_eq!(err.code(), McpErrorCode::ConnectionError.code());
548 assert_eq!(err.message(), "test connection error");
549 }
550
551 #[test]
552 fn test_timeout_error() {
553 let err = McpError::timeout("request timed out", Duration::from_secs(30));
554 assert_eq!(err.code(), McpErrorCode::TimeoutError.code());
555 assert!(err.message().contains("request timed out"));
556 }
557
558 #[test]
559 fn test_validation_error_with_details() {
560 let err = McpError::validation(
561 "invalid configuration",
562 vec!["missing field: command".to_string()],
563 );
564 assert_eq!(err.code(), McpErrorCode::ValidationError.code());
565
566 if let McpError::Validation { errors, .. } = err {
567 assert_eq!(errors.len(), 1);
568 assert!(errors[0].contains("missing field"));
569 } else {
570 panic!("Expected Validation error");
571 }
572 }
573
574 #[test]
575 fn test_server_error() {
576 let err = McpError::server(-32001, "server unavailable", None);
577 assert_eq!(err.code(), -32001);
578 assert_eq!(err.message(), "server unavailable");
579 }
580
581 #[test]
582 fn test_io_error_conversion() {
583 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
584 let mcp_err: McpError = io_err.into();
585 assert_eq!(mcp_err.code(), McpErrorCode::InternalError.code());
586 assert!(mcp_err.message().contains("file not found"));
587 }
588
589 #[test]
590 fn test_error_display() {
591 let err = McpError::connection("failed to connect");
592 let display = format!("{}", err);
593 assert!(display.contains("Connection error"));
594 assert!(display.contains("failed to connect"));
595 }
596
597 #[test]
598 fn test_structured_error_new() {
599 let err = StructuredError::new(-32000, "test error");
600 assert_eq!(err.code, -32000);
601 assert_eq!(err.message, "test error");
602 assert!(err.data.is_none());
603 }
604
605 #[test]
606 fn test_structured_error_with_data() {
607 let data = serde_json::json!({"key": "value"});
608 let err = StructuredError::with_data(-32000, "test error", data.clone());
609 assert_eq!(err.code, -32000);
610 assert_eq!(err.message, "test error");
611 assert_eq!(err.data, Some(data));
612 }
613
614 #[test]
615 fn test_structured_error_from_mcp_error() {
616 let mcp_err = McpError::connection("connection failed");
617 let structured: StructuredError = (&mcp_err).into();
618
619 assert_eq!(structured.code, McpErrorCode::ConnectionError.code());
620 assert_eq!(structured.message, "connection failed");
621 }
622
623 #[test]
624 fn test_structured_error_from_validation_error() {
625 let mcp_err = McpError::validation(
626 "invalid config",
627 vec!["missing field".to_string(), "invalid value".to_string()],
628 );
629 let structured: StructuredError = (&mcp_err).into();
630
631 assert_eq!(structured.code, McpErrorCode::ValidationError.code());
632 assert_eq!(structured.message, "invalid config");
633 assert!(structured.data.is_some());
634
635 let data = structured.data.unwrap();
636 let errors = data.get("errors").unwrap().as_array().unwrap();
637 assert_eq!(errors.len(), 2);
638 }
639
640 #[test]
641 fn test_structured_error_from_timeout_error() {
642 let mcp_err = McpError::timeout("request timed out", Duration::from_secs(30));
643 let structured: StructuredError = (&mcp_err).into();
644
645 assert_eq!(structured.code, McpErrorCode::TimeoutError.code());
646 assert!(structured.data.is_some());
647
648 let data = structured.data.unwrap();
649 assert_eq!(data.get("duration_ms").unwrap().as_u64().unwrap(), 30000);
650 }
651
652 #[test]
653 fn test_structured_error_serialization() {
654 let err = StructuredError::new(-32000, "test error");
655 let json = serde_json::to_string(&err).unwrap();
656
657 assert!(json.contains("\"code\":-32000"));
658 assert!(json.contains("\"message\":\"test error\""));
659 assert!(!json.contains("\"data\""));
661 }
662
663 #[test]
664 fn test_all_error_variants_have_code_and_message() {
665 let errors: Vec<McpError> = vec![
669 McpError::connection("test"),
670 McpError::transport("test"),
671 McpError::protocol("test"),
672 McpError::timeout("test", Duration::from_secs(1)),
673 McpError::cancelled("test", None),
674 McpError::server(-32000, "test", None),
675 McpError::validation("test", vec![]),
676 McpError::config("test"),
677 McpError::lifecycle("test", None),
678 McpError::tool("test", None),
679 McpError::permission_denied("test"),
680 ];
681
682 for err in errors {
683 let structured: StructuredError = (&err).into();
684 assert!(structured.code != 0, "Error code should not be 0");
685 assert!(
686 !structured.message.is_empty(),
687 "Error message should not be empty"
688 );
689 }
690 }
691}