1use serde::{Deserialize, Serialize};
32
33pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[repr(i32)]
43pub enum ErrorCode {
44 ParseError = -32700,
46 InvalidRequest = -32600,
48 MethodNotFound = -32601,
50 InvalidParams = -32602,
52 InternalError = -32603,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[repr(i32)]
59pub enum McpErrorCode {
60 ConnectionClosed = -32000,
62 RequestTimeout = -32001,
64 ResourceNotFound = -32002,
66 AlreadySubscribed = -32003,
68 NotSubscribed = -32004,
70 SessionNotFound = -32005,
72 SessionRequired = -32006,
74 Forbidden = -32007,
76}
77
78impl McpErrorCode {
79 pub fn code(self) -> i32 {
80 self as i32
81 }
82}
83
84impl ErrorCode {
85 pub fn code(self) -> i32 {
86 self as i32
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct JsonRpcError {
93 pub code: i32,
94 pub message: String,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub data: Option<serde_json::Value>,
97}
98
99impl JsonRpcError {
100 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
101 Self {
102 code: code.code(),
103 message: message.into(),
104 data: None,
105 }
106 }
107
108 pub fn with_data(mut self, data: serde_json::Value) -> Self {
109 self.data = Some(data);
110 self
111 }
112
113 pub fn parse_error(message: impl Into<String>) -> Self {
114 Self::new(ErrorCode::ParseError, message)
115 }
116
117 pub fn invalid_request(message: impl Into<String>) -> Self {
118 Self::new(ErrorCode::InvalidRequest, message)
119 }
120
121 pub fn method_not_found(method: &str) -> Self {
122 Self::new(
123 ErrorCode::MethodNotFound,
124 format!("Method not found: {}", method),
125 )
126 }
127
128 pub fn invalid_params(message: impl Into<String>) -> Self {
129 Self::new(ErrorCode::InvalidParams, message)
130 }
131
132 pub fn internal_error(message: impl Into<String>) -> Self {
133 Self::new(ErrorCode::InternalError, message)
134 }
135
136 pub fn mcp_error(code: McpErrorCode, message: impl Into<String>) -> Self {
138 Self {
139 code: code.code(),
140 message: message.into(),
141 data: None,
142 }
143 }
144
145 pub fn connection_closed(message: impl Into<String>) -> Self {
147 Self::mcp_error(McpErrorCode::ConnectionClosed, message)
148 }
149
150 pub fn request_timeout(message: impl Into<String>) -> Self {
152 Self::mcp_error(McpErrorCode::RequestTimeout, message)
153 }
154
155 pub fn resource_not_found(uri: &str) -> Self {
157 Self::mcp_error(
158 McpErrorCode::ResourceNotFound,
159 format!("Resource not found: {}", uri),
160 )
161 }
162
163 pub fn already_subscribed(uri: &str) -> Self {
165 Self::mcp_error(
166 McpErrorCode::AlreadySubscribed,
167 format!("Already subscribed to: {}", uri),
168 )
169 }
170
171 pub fn not_subscribed(uri: &str) -> Self {
173 Self::mcp_error(
174 McpErrorCode::NotSubscribed,
175 format!("Not subscribed to: {}", uri),
176 )
177 }
178
179 pub fn session_not_found() -> Self {
184 Self::mcp_error(
185 McpErrorCode::SessionNotFound,
186 "Session not found or expired. Please re-initialize the connection.",
187 )
188 }
189
190 pub fn session_not_found_with_id(session_id: &str) -> Self {
192 Self::mcp_error(
193 McpErrorCode::SessionNotFound,
194 format!(
195 "Session '{}' not found or expired. Please re-initialize the connection.",
196 session_id
197 ),
198 )
199 }
200
201 pub fn session_required() -> Self {
203 Self::mcp_error(
204 McpErrorCode::SessionRequired,
205 "MCP-Session-Id header is required for this request.",
206 )
207 }
208
209 pub fn forbidden(message: impl Into<String>) -> Self {
211 Self::mcp_error(McpErrorCode::Forbidden, message)
212 }
213}
214
215#[derive(Debug)]
217pub struct ToolError {
218 pub tool: Option<String>,
220 pub message: String,
222 pub source: Option<BoxError>,
224}
225
226impl std::fmt::Display for ToolError {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 if let Some(tool) = &self.tool {
229 write!(f, "Tool '{}' error: {}", tool, self.message)
230 } else {
231 write!(f, "Tool error: {}", self.message)
232 }
233 }
234}
235
236impl std::error::Error for ToolError {
237 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238 self.source
239 .as_ref()
240 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
241 }
242}
243
244impl ToolError {
245 pub fn new(message: impl Into<String>) -> Self {
247 Self {
248 tool: None,
249 message: message.into(),
250 source: None,
251 }
252 }
253
254 pub fn with_tool(tool: impl Into<String>, message: impl Into<String>) -> Self {
256 Self {
257 tool: Some(tool.into()),
258 message: message.into(),
259 source: None,
260 }
261 }
262
263 pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
265 self.source = Some(Box::new(source));
266 self
267 }
268}
269
270#[derive(Debug, thiserror::Error)]
272pub enum Error {
273 #[error("JSON-RPC error: {0:?}")]
274 JsonRpc(JsonRpcError),
275
276 #[error("Serialization error: {0}")]
277 Serialization(#[from] serde_json::Error),
278
279 #[error("{0}")]
285 Tool(#[from] ToolError),
286
287 #[error("Transport error: {0}")]
288 Transport(String),
289
290 #[error("Internal error: {0}")]
291 Internal(String),
292}
293
294impl Error {
295 pub fn tool(message: impl Into<String>) -> Self {
297 Error::Tool(ToolError::new(message))
298 }
299
300 pub fn tool_with_name(tool: impl Into<String>, message: impl Into<String>) -> Self {
302 Error::Tool(ToolError::with_tool(tool, message))
303 }
304
305 pub fn tool_from<E: std::fmt::Display>(err: E) -> Self {
318 Error::Tool(ToolError::new(err.to_string()))
319 }
320
321 pub fn tool_context<E: std::fmt::Display>(context: impl Into<String>, err: E) -> Self {
334 Error::Tool(ToolError::new(format!("{}: {}", context.into(), err)))
335 }
336
337 pub fn invalid_params(message: impl Into<String>) -> Self {
346 Error::JsonRpc(JsonRpcError::invalid_params(message))
347 }
348
349 pub fn internal(message: impl Into<String>) -> Self {
358 Error::JsonRpc(JsonRpcError::internal_error(message))
359 }
360}
361
362impl From<JsonRpcError> for Error {
363 fn from(err: JsonRpcError) -> Self {
364 Error::JsonRpc(err)
365 }
366}
367
368pub type Result<T> = std::result::Result<T, Error>;
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_box_error_from_io_error() {
377 let io_err = std::io::Error::other("disk full");
378 let boxed: BoxError = io_err.into();
379 assert_eq!(boxed.to_string(), "disk full");
380 }
381
382 #[test]
383 fn test_box_error_from_string() {
384 let err: BoxError = "something went wrong".into();
385 assert_eq!(err.to_string(), "something went wrong");
386 }
387
388 #[test]
389 fn test_box_error_is_send_sync() {
390 fn assert_send_sync<T: Send + Sync>() {}
391 assert_send_sync::<BoxError>();
392 }
393
394 #[test]
395 fn test_tool_error_source_uses_box_error() {
396 let io_err = std::io::Error::other("timeout");
397 let tool_err = ToolError::new("failed").with_source(io_err);
398 assert!(tool_err.source.is_some());
399 assert_eq!(tool_err.source.unwrap().to_string(), "timeout");
400 }
401}