1use serde::{Deserialize, Serialize};
33
34pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[repr(i32)]
44#[non_exhaustive]
45pub enum ErrorCode {
46 ParseError = -32700,
48 InvalidRequest = -32600,
50 MethodNotFound = -32601,
52 InvalidParams = -32602,
54 InternalError = -32603,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[repr(i32)]
61#[non_exhaustive]
62pub enum McpErrorCode {
63 ConnectionClosed = -32000,
65 RequestTimeout = -32001,
67 ResourceNotFound = -32002,
69 AlreadySubscribed = -32003,
71 NotSubscribed = -32004,
73 SessionNotFound = -32005,
75 SessionRequired = -32006,
77 Forbidden = -32007,
79 UrlElicitationRequired = -32042,
81}
82
83impl McpErrorCode {
84 pub fn code(self) -> i32 {
85 self as i32
86 }
87}
88
89impl ErrorCode {
90 pub fn code(self) -> i32 {
91 self as i32
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct JsonRpcError {
98 pub code: i32,
99 pub message: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub data: Option<serde_json::Value>,
102}
103
104impl JsonRpcError {
105 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
106 Self {
107 code: code.code(),
108 message: message.into(),
109 data: None,
110 }
111 }
112
113 pub fn with_data(mut self, data: serde_json::Value) -> Self {
114 self.data = Some(data);
115 self
116 }
117
118 pub fn parse_error(message: impl Into<String>) -> Self {
119 Self::new(ErrorCode::ParseError, message)
120 }
121
122 pub fn invalid_request(message: impl Into<String>) -> Self {
123 Self::new(ErrorCode::InvalidRequest, message)
124 }
125
126 pub fn method_not_found(method: &str) -> Self {
127 Self::new(
128 ErrorCode::MethodNotFound,
129 format!("Method not found: {}", method),
130 )
131 }
132
133 pub fn invalid_params(message: impl Into<String>) -> Self {
134 Self::new(ErrorCode::InvalidParams, message)
135 }
136
137 pub fn internal_error(message: impl Into<String>) -> Self {
138 Self::new(ErrorCode::InternalError, message)
139 }
140
141 pub fn mcp_error(code: McpErrorCode, message: impl Into<String>) -> Self {
143 Self {
144 code: code.code(),
145 message: message.into(),
146 data: None,
147 }
148 }
149
150 pub fn connection_closed(message: impl Into<String>) -> Self {
152 Self::mcp_error(McpErrorCode::ConnectionClosed, message)
153 }
154
155 pub fn request_timeout(message: impl Into<String>) -> Self {
157 Self::mcp_error(McpErrorCode::RequestTimeout, message)
158 }
159
160 pub fn resource_not_found(uri: &str) -> Self {
162 Self::mcp_error(
163 McpErrorCode::ResourceNotFound,
164 format!("Resource not found: {}", uri),
165 )
166 }
167
168 pub fn already_subscribed(uri: &str) -> Self {
170 Self::mcp_error(
171 McpErrorCode::AlreadySubscribed,
172 format!("Already subscribed to: {}", uri),
173 )
174 }
175
176 pub fn not_subscribed(uri: &str) -> Self {
178 Self::mcp_error(
179 McpErrorCode::NotSubscribed,
180 format!("Not subscribed to: {}", uri),
181 )
182 }
183
184 pub fn session_not_found() -> Self {
189 Self::mcp_error(
190 McpErrorCode::SessionNotFound,
191 "Session not found or expired. Please re-initialize the connection.",
192 )
193 }
194
195 pub fn session_not_found_with_id(session_id: &str) -> Self {
197 Self::mcp_error(
198 McpErrorCode::SessionNotFound,
199 format!(
200 "Session '{}' not found or expired. Please re-initialize the connection.",
201 session_id
202 ),
203 )
204 }
205
206 pub fn session_required() -> Self {
208 Self::mcp_error(
209 McpErrorCode::SessionRequired,
210 "MCP-Session-Id header is required for this request.",
211 )
212 }
213
214 pub fn forbidden(message: impl Into<String>) -> Self {
216 Self::mcp_error(McpErrorCode::Forbidden, message)
217 }
218
219 pub fn url_elicitation_required(message: impl Into<String>) -> Self {
221 Self::mcp_error(McpErrorCode::UrlElicitationRequired, message)
222 }
223}
224
225#[derive(Debug)]
227pub struct ToolError {
228 pub tool: Option<String>,
230 pub message: String,
232 pub source: Option<BoxError>,
234}
235
236impl std::fmt::Display for ToolError {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 if let Some(tool) = &self.tool {
239 write!(f, "Tool '{}' error: {}", tool, self.message)
240 } else {
241 write!(f, "Tool error: {}", self.message)
242 }
243 }
244}
245
246impl std::error::Error for ToolError {
247 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
248 self.source
249 .as_ref()
250 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
251 }
252}
253
254impl ToolError {
255 pub fn new(message: impl Into<String>) -> Self {
257 Self {
258 tool: None,
259 message: message.into(),
260 source: None,
261 }
262 }
263
264 pub fn with_tool(tool: impl Into<String>, message: impl Into<String>) -> Self {
266 Self {
267 tool: Some(tool.into()),
268 message: message.into(),
269 source: None,
270 }
271 }
272
273 pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
275 self.source = Some(Box::new(source));
276 self
277 }
278}
279
280#[derive(Debug, thiserror::Error)]
282#[non_exhaustive]
283pub enum Error {
284 #[error("JSON-RPC error: {0:?}")]
285 JsonRpc(JsonRpcError),
286
287 #[error("Serialization error: {0}")]
288 Serialization(#[from] serde_json::Error),
289
290 #[error("{0}")]
296 Tool(#[from] ToolError),
297
298 #[error("Transport error: {0}")]
299 Transport(String),
300
301 #[error("Internal error: {0}")]
302 Internal(String),
303}
304
305impl Error {
306 pub fn tool(message: impl Into<String>) -> Self {
308 Error::Tool(ToolError::new(message))
309 }
310
311 pub fn tool_with_name(tool: impl Into<String>, message: impl Into<String>) -> Self {
313 Error::Tool(ToolError::with_tool(tool, message))
314 }
315
316 pub fn tool_from<E: std::fmt::Display>(err: E) -> Self {
329 Error::Tool(ToolError::new(err.to_string()))
330 }
331
332 pub fn tool_context<E: std::fmt::Display>(context: impl Into<String>, err: E) -> Self {
347 Error::Tool(ToolError::new(format!("{}: {}", context.into(), err)))
348 }
349
350 pub fn invalid_params(message: impl Into<String>) -> Self {
359 Error::JsonRpc(JsonRpcError::invalid_params(message))
360 }
361
362 pub fn internal(message: impl Into<String>) -> Self {
371 Error::JsonRpc(JsonRpcError::internal_error(message))
372 }
373}
374
375pub trait ResultExt<T> {
394 fn tool_err(self) -> std::result::Result<T, Error>;
405
406 fn tool_context(self, context: impl Into<String>) -> std::result::Result<T, Error>;
417}
418
419impl<T, E: std::fmt::Display> ResultExt<T> for std::result::Result<T, E> {
420 fn tool_err(self) -> std::result::Result<T, Error> {
421 self.map_err(Error::tool_from)
422 }
423
424 fn tool_context(self, context: impl Into<String>) -> std::result::Result<T, Error> {
425 self.map_err(|e| Error::tool_context(context, e))
426 }
427}
428
429impl From<JsonRpcError> for Error {
430 fn from(err: JsonRpcError) -> Self {
431 Error::JsonRpc(err)
432 }
433}
434
435impl From<std::convert::Infallible> for Error {
436 fn from(err: std::convert::Infallible) -> Self {
437 match err {}
438 }
439}
440
441pub type Result<T> = std::result::Result<T, Error>;
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn test_box_error_from_io_error() {
450 let io_err = std::io::Error::other("disk full");
451 let boxed: BoxError = io_err.into();
452 assert_eq!(boxed.to_string(), "disk full");
453 }
454
455 #[test]
456 fn test_box_error_from_string() {
457 let err: BoxError = "something went wrong".into();
458 assert_eq!(err.to_string(), "something went wrong");
459 }
460
461 #[test]
462 fn test_box_error_is_send_sync() {
463 fn assert_send_sync<T: Send + Sync>() {}
464 assert_send_sync::<BoxError>();
465 }
466
467 #[test]
468 fn test_tool_error_source_uses_box_error() {
469 let io_err = std::io::Error::other("timeout");
470 let tool_err = ToolError::new("failed").with_source(io_err);
471 assert!(tool_err.source.is_some());
472 assert_eq!(tool_err.source.unwrap().to_string(), "timeout");
473 }
474
475 #[test]
476 fn test_result_ext_tool_err() {
477 let result: std::result::Result<(), std::io::Error> =
478 Err(std::io::Error::other("disk full"));
479 let err = result.tool_err().unwrap_err();
480 assert!(matches!(err, Error::Tool(_)));
481 assert!(err.to_string().contains("disk full"));
482 }
483
484 #[test]
485 fn test_result_ext_tool_context() {
486 let result: std::result::Result<(), std::io::Error> =
487 Err(std::io::Error::other("connection refused"));
488 let err = result.tool_context("database query failed").unwrap_err();
489 assert!(matches!(err, Error::Tool(_)));
490 assert!(err.to_string().contains("database query failed"));
491 assert!(err.to_string().contains("connection refused"));
492 }
493
494 #[test]
495 fn test_result_ext_ok_passes_through() {
496 let result: std::result::Result<i32, std::io::Error> = Ok(42);
497 assert_eq!(result.tool_err().unwrap(), 42);
498 let result: std::result::Result<i32, std::io::Error> = Ok(42);
499 assert_eq!(result.tool_context("should not appear").unwrap(), 42);
500 }
501}