turul_mcp_client/
error.rs

1//! Error types for MCP client operations
2
3use serde_json::Value;
4use thiserror::Error;
5
6/// Result type for MCP client operations
7pub type McpClientResult<T> = Result<T, McpClientError>;
8
9/// Comprehensive error type for MCP client operations
10#[derive(Error, Debug)]
11pub enum McpClientError {
12    /// Transport-level errors
13    #[error("Transport error: {0}")]
14    Transport(#[from] TransportError),
15
16    /// Protocol-level errors
17    #[error("Protocol error: {0}")]
18    Protocol(#[from] ProtocolError),
19
20    /// Session management errors
21    #[error("Session error: {0}")]
22    Session(#[from] SessionError),
23
24    /// Authentication/authorization errors
25    #[error("Authentication error: {0}")]
26    Auth(String),
27
28    /// Configuration errors
29    #[error("Configuration error: {0}")]
30    Config(String),
31
32    /// Network/connection errors
33    #[error("Connection error: {0}")]
34    Connection(#[from] reqwest::Error),
35
36    /// JSON parsing errors
37    #[error("JSON error: {0}")]
38    Json(#[from] serde_json::Error),
39
40    /// Timeout errors
41    #[error("Operation timed out")]
42    Timeout,
43
44    /// Server returned an error
45    #[error("Server error (code {code}): {message}")]
46    ServerError {
47        code: i32,
48        message: String,
49        data: Option<Value>,
50    },
51
52    /// Generic error with context
53    #[error("Error: {message}")]
54    Generic { message: String },
55}
56
57/// Transport-specific errors
58#[derive(Error, Debug)]
59pub enum TransportError {
60    #[error("HTTP transport error: {0}")]
61    Http(String),
62
63    #[error("SSE transport error: {0}")]
64    Sse(String),
65
66    #[error("WebSocket transport error: {0}")]
67    WebSocket(String),
68
69    #[error("Stdio transport error: {0}")]
70    Stdio(String),
71
72    #[error("Unsupported transport: {0}")]
73    Unsupported(String),
74
75    #[error("Connection failed: {0}")]
76    ConnectionFailed(String),
77
78    #[error("Transport closed unexpectedly")]
79    Closed,
80}
81
82/// Protocol-specific errors
83#[derive(Error, Debug)]
84pub enum ProtocolError {
85    #[error("Invalid JSON-RPC request: {0}")]
86    InvalidRequest(String),
87
88    #[error("Invalid JSON-RPC response: {0}")]
89    InvalidResponse(String),
90
91    #[error("Unsupported protocol version: {0}")]
92    UnsupportedVersion(String),
93
94    #[error("Method not found: {0}")]
95    MethodNotFound(String),
96
97    #[error("Invalid parameters: {0}")]
98    InvalidParams(String),
99
100    #[error("Protocol negotiation failed: {0}")]
101    NegotiationFailed(String),
102
103    #[error("Capability mismatch: {0}")]
104    CapabilityMismatch(String),
105}
106
107/// Session management errors
108#[derive(Error, Debug)]
109pub enum SessionError {
110    #[error("Session not initialized")]
111    NotInitialized,
112
113    #[error("Session already initialized")]
114    AlreadyInitialized,
115
116    #[error("Session expired")]
117    Expired,
118
119    #[error("Session terminated")]
120    Terminated,
121
122    #[error("Invalid session state: expected {expected}, found {actual}")]
123    InvalidState { expected: String, actual: String },
124
125    #[error("Session recovery failed: {0}")]
126    RecoveryFailed(String),
127}
128
129impl McpClientError {
130    /// Create a generic error with a message
131    pub fn generic(message: impl Into<String>) -> Self {
132        Self::Generic {
133            message: message.into(),
134        }
135    }
136
137    /// Create a configuration error
138    pub fn config(message: impl Into<String>) -> Self {
139        Self::Config(message.into())
140    }
141
142    /// Create an authentication error
143    pub fn auth(message: impl Into<String>) -> Self {
144        Self::Auth(message.into())
145    }
146
147    /// Create a server error from JSON-RPC error response
148    pub fn server_error(code: i32, message: impl Into<String>, data: Option<Value>) -> Self {
149        Self::ServerError {
150            code,
151            message: message.into(),
152            data,
153        }
154    }
155
156    /// Check if the error is retryable
157    pub fn is_retryable(&self) -> bool {
158        match self {
159            Self::Transport(TransportError::ConnectionFailed(_)) => true,
160            Self::Transport(TransportError::Closed) => true,
161            Self::Connection(_) => true,
162            Self::Timeout => true,
163            Self::ServerError { code, .. } => {
164                // Retry on server errors that might be temporary
165                matches!(code, -32099..=-32000) // Implementation-defined server errors
166            }
167            _ => false,
168        }
169    }
170
171    /// Check if the error is a protocol-level issue
172    pub fn is_protocol_error(&self) -> bool {
173        matches!(self, Self::Protocol(_))
174    }
175
176    /// Check if the error is a session-level issue
177    pub fn is_session_error(&self) -> bool {
178        matches!(self, Self::Session(_))
179    }
180
181    /// Get the error code if this is a server error
182    pub fn error_code(&self) -> Option<i32> {
183        match self {
184            Self::ServerError { code, .. } => Some(*code),
185            _ => None,
186        }
187    }
188}
189
190// From implementations are handled by #[from] in the enum
191
192/// Convenience macro for creating generic errors
193#[macro_export]
194macro_rules! client_error {
195    ($($arg:tt)*) => {
196        $crate::error::McpClientError::generic(format!($($arg)*))
197    };
198}