turul_mcp_client/
error.rs1use serde_json::Value;
4use thiserror::Error;
5
6pub type McpClientResult<T> = Result<T, McpClientError>;
8
9#[derive(Error, Debug)]
11pub enum McpClientError {
12 #[error("Transport error: {0}")]
14 Transport(#[from] TransportError),
15
16 #[error("Protocol error: {0}")]
18 Protocol(#[from] ProtocolError),
19
20 #[error("Session error: {0}")]
22 Session(#[from] SessionError),
23
24 #[error("Authentication error: {0}")]
26 Auth(String),
27
28 #[error("Configuration error: {0}")]
30 Config(String),
31
32 #[error("Connection error: {0}")]
34 Connection(#[from] reqwest::Error),
35
36 #[error("JSON error: {0}")]
38 Json(#[from] serde_json::Error),
39
40 #[error("Operation timed out")]
42 Timeout,
43
44 #[error("Server error (code {code}): {message}")]
46 ServerError {
47 code: i32,
48 message: String,
49 data: Option<Value>,
50 },
51
52 #[error("Error: {message}")]
54 Generic { message: String },
55}
56
57#[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#[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#[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 pub fn generic(message: impl Into<String>) -> Self {
132 Self::Generic {
133 message: message.into(),
134 }
135 }
136
137 pub fn config(message: impl Into<String>) -> Self {
139 Self::Config(message.into())
140 }
141
142 pub fn auth(message: impl Into<String>) -> Self {
144 Self::Auth(message.into())
145 }
146
147 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 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 matches!(code, -32099..=-32000) }
167 _ => false,
168 }
169 }
170
171 pub fn is_protocol_error(&self) -> bool {
173 matches!(self, Self::Protocol(_))
174 }
175
176 pub fn is_session_error(&self) -> bool {
178 matches!(self, Self::Session(_))
179 }
180
181 pub fn error_code(&self) -> Option<i32> {
183 match self {
184 Self::ServerError { code, .. } => Some(*code),
185 _ => None,
186 }
187 }
188}
189
190#[macro_export]
194macro_rules! client_error {
195 ($($arg:tt)*) => {
196 $crate::error::McpClientError::generic(format!($($arg)*))
197 };
198}