pulseengine_mcp_protocol/
error.rs

1//! Error types for the MCP protocol
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Result type alias for MCP protocol operations
7///
8/// Note: Use `McpResult` instead of `Result` to avoid conflicts with std::result::Result
9pub type Result<T> = std::result::Result<T, Error>;
10
11/// Preferred result type alias that doesn't conflict with std::result::Result
12pub type McpResult<T> = std::result::Result<T, Error>;
13
14/// Core MCP error type
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, thiserror::Error)]
16pub struct Error {
17    /// Error code following MCP specification
18    pub code: ErrorCode,
19    /// Human-readable error message
20    pub message: String,
21    /// Optional additional error data
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub data: Option<serde_json::Value>,
24}
25
26impl fmt::Display for Error {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "{}: {}", self.code, self.message)
29    }
30}
31
32impl Error {
33    /// Create a new error with the given code and message
34    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
35        Self {
36            code,
37            message: message.into(),
38            data: None,
39        }
40    }
41
42    /// Create an error with additional data
43    pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
44        Self {
45            code,
46            message: message.into(),
47            data: Some(data),
48        }
49    }
50
51    /// Create a parse error
52    pub fn parse_error(message: impl Into<String>) -> Self {
53        Self::new(ErrorCode::ParseError, message)
54    }
55
56    /// Create an invalid request error
57    pub fn invalid_request(message: impl Into<String>) -> Self {
58        Self::new(ErrorCode::InvalidRequest, message)
59    }
60
61    /// Create a method not found error
62    pub fn method_not_found(method: impl Into<String>) -> Self {
63        Self::new(
64            ErrorCode::MethodNotFound,
65            format!("Method not found: {}", method.into()),
66        )
67    }
68
69    /// Create an invalid params error
70    pub fn invalid_params(message: impl Into<String>) -> Self {
71        Self::new(ErrorCode::InvalidParams, message)
72    }
73
74    /// Create an internal error
75    pub fn internal_error(message: impl Into<String>) -> Self {
76        Self::new(ErrorCode::InternalError, message)
77    }
78
79    /// Create a protocol version mismatch error
80    pub fn protocol_version_mismatch(client_version: &str, server_version: &str) -> Self {
81        Self::with_data(
82            ErrorCode::InvalidRequest,
83            format!("Protocol version mismatch: client={client_version}, server={server_version}"),
84            serde_json::json!({
85                "client_version": client_version,
86                "server_version": server_version
87            }),
88        )
89    }
90
91    /// Create an authorization error
92    pub fn unauthorized(message: impl Into<String>) -> Self {
93        Self::new(ErrorCode::Unauthorized, message)
94    }
95
96    /// Create a forbidden error
97    pub fn forbidden(message: impl Into<String>) -> Self {
98        Self::new(ErrorCode::Forbidden, message)
99    }
100
101    /// Create a resource not found error
102    pub fn resource_not_found(resource: impl Into<String>) -> Self {
103        Self::new(
104            ErrorCode::ResourceNotFound,
105            format!("Resource not found: {}", resource.into()),
106        )
107    }
108
109    /// Create a tool not found error
110    pub fn tool_not_found(tool: impl Into<String>) -> Self {
111        Self::new(
112            ErrorCode::ToolNotFound,
113            format!("Tool not found: {}", tool.into()),
114        )
115    }
116
117    /// Create a validation error
118    pub fn validation_error(message: impl Into<String>) -> Self {
119        Self::new(ErrorCode::ValidationError, message)
120    }
121
122    /// Create a rate limit exceeded error
123    pub fn rate_limit_exceeded(message: impl Into<String>) -> Self {
124        Self::new(ErrorCode::RateLimitExceeded, message)
125    }
126
127    /// Create a URL elicitation required error (MCP 2025-11-25)
128    ///
129    /// This error indicates that a request requires URL mode elicitation
130    /// before it can proceed. The client should present the elicitation
131    /// URLs to the user and retry after completion.
132    pub fn url_elicitation_required(
133        message: impl Into<String>,
134        elicitations: Vec<crate::UrlElicitationInfo>,
135    ) -> Self {
136        let data = crate::UrlElicitationRequiredData { elicitations };
137        Self::with_data(
138            ErrorCode::UrlElicitationRequired,
139            message,
140            serde_json::to_value(data).unwrap_or_default(),
141        )
142    }
143}
144
145/// MCP error codes following JSON-RPC 2.0 specification
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147#[repr(i32)]
148pub enum ErrorCode {
149    // Standard JSON-RPC 2.0 errors
150    ParseError = -32700,
151    InvalidRequest = -32600,
152    MethodNotFound = -32601,
153    InvalidParams = -32602,
154    InternalError = -32603,
155
156    // MCP-specific errors
157    Unauthorized = -32000,
158    Forbidden = -32001,
159    ResourceNotFound = -32002,
160    ToolNotFound = -32003,
161    ValidationError = -32004,
162    RateLimitExceeded = -32005,
163
164    // MCP 2025-11-25 errors
165    /// URL elicitation required before request can proceed
166    UrlElicitationRequired = -32042,
167}
168
169impl Serialize for ErrorCode {
170    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
171    where
172        S: serde::Serializer,
173    {
174        serializer.serialize_i32(*self as i32)
175    }
176}
177
178impl<'de> Deserialize<'de> for ErrorCode {
179    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
180    where
181        D: serde::Deserializer<'de>,
182    {
183        let code = i32::deserialize(deserializer)?;
184        match code {
185            -32700 => Ok(ErrorCode::ParseError),
186            -32600 => Ok(ErrorCode::InvalidRequest),
187            -32601 => Ok(ErrorCode::MethodNotFound),
188            -32602 => Ok(ErrorCode::InvalidParams),
189            -32603 => Ok(ErrorCode::InternalError),
190            -32000 => Ok(ErrorCode::Unauthorized),
191            -32001 => Ok(ErrorCode::Forbidden),
192            -32002 => Ok(ErrorCode::ResourceNotFound),
193            -32003 => Ok(ErrorCode::ToolNotFound),
194            -32004 => Ok(ErrorCode::ValidationError),
195            -32005 => Ok(ErrorCode::RateLimitExceeded),
196            -32042 => Ok(ErrorCode::UrlElicitationRequired),
197            _ => Err(serde::de::Error::custom(format!(
198                "Unknown error code: {code}"
199            ))),
200        }
201    }
202}
203
204impl fmt::Display for ErrorCode {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        let name = match self {
207            ErrorCode::ParseError => "ParseError",
208            ErrorCode::InvalidRequest => "InvalidRequest",
209            ErrorCode::MethodNotFound => "MethodNotFound",
210            ErrorCode::InvalidParams => "InvalidParams",
211            ErrorCode::InternalError => "InternalError",
212            ErrorCode::Unauthorized => "Unauthorized",
213            ErrorCode::Forbidden => "Forbidden",
214            ErrorCode::ResourceNotFound => "ResourceNotFound",
215            ErrorCode::ToolNotFound => "ToolNotFound",
216            ErrorCode::ValidationError => "ValidationError",
217            ErrorCode::RateLimitExceeded => "RateLimitExceeded",
218            ErrorCode::UrlElicitationRequired => "UrlElicitationRequired",
219        };
220        write!(f, "{name}")
221    }
222}
223
224// Implement conversion from common error types
225impl From<serde_json::Error> for Error {
226    fn from(err: serde_json::Error) -> Self {
227        Error::parse_error(err.to_string())
228    }
229}
230
231impl From<uuid::Error> for Error {
232    fn from(err: uuid::Error) -> Self {
233        Error::validation_error(format!("Invalid UUID: {err}"))
234    }
235}
236
237impl From<validator::ValidationErrors> for Error {
238    fn from(err: validator::ValidationErrors) -> Self {
239        Error::validation_error(err.to_string())
240    }
241}
242
243#[cfg(feature = "logging")]
244impl From<pulseengine_mcp_logging::LoggingError> for Error {
245    fn from(err: pulseengine_mcp_logging::LoggingError) -> Self {
246        match err {
247            pulseengine_mcp_logging::LoggingError::Config(msg) => {
248                Error::invalid_request(format!("Logging config: {msg}"))
249            }
250            pulseengine_mcp_logging::LoggingError::Io(io_err) => {
251                Error::internal_error(format!("Logging I/O: {io_err}"))
252            }
253            pulseengine_mcp_logging::LoggingError::Serialization(serde_err) => {
254                Error::internal_error(format!("Logging serialization: {serde_err}"))
255            }
256            pulseengine_mcp_logging::LoggingError::Tracing(msg) => {
257                Error::internal_error(format!("Tracing: {msg}"))
258            }
259        }
260    }
261}
262
263// Optional ErrorClassification implementation when logging feature is enabled
264#[cfg(feature = "logging")]
265impl pulseengine_mcp_logging::ErrorClassification for Error {
266    fn error_type(&self) -> &str {
267        match self.code {
268            ErrorCode::ParseError => "parse_error",
269            ErrorCode::InvalidRequest => "invalid_request",
270            ErrorCode::MethodNotFound => "method_not_found",
271            ErrorCode::InvalidParams => "invalid_params",
272            ErrorCode::InternalError => "internal_error",
273            ErrorCode::Unauthorized => "unauthorized",
274            ErrorCode::Forbidden => "forbidden",
275            ErrorCode::ResourceNotFound => "resource_not_found",
276            ErrorCode::ToolNotFound => "tool_not_found",
277            ErrorCode::ValidationError => "validation_error",
278            ErrorCode::RateLimitExceeded => "rate_limit_exceeded",
279            ErrorCode::UrlElicitationRequired => "url_elicitation_required",
280        }
281    }
282
283    fn is_retryable(&self) -> bool {
284        // UrlElicitationRequired is retryable after the elicitation completes
285        matches!(
286            self.code,
287            ErrorCode::InternalError
288                | ErrorCode::RateLimitExceeded
289                | ErrorCode::UrlElicitationRequired
290        )
291    }
292
293    fn is_timeout(&self) -> bool {
294        false // Protocol errors don't directly represent timeouts
295    }
296
297    fn is_auth_error(&self) -> bool {
298        matches!(self.code, ErrorCode::Unauthorized | ErrorCode::Forbidden)
299    }
300
301    fn is_connection_error(&self) -> bool {
302        false // Protocol errors don't directly represent connection errors
303    }
304}