use thiserror::Error;
#[derive(Debug, Error)]
pub enum McpError {
#[error("MCP client error: {0}")]
#[cfg(feature = "mcp")]
Client(#[from] crate::mcp::minimal::MinimalMcpError),
#[error("Transport error: {0}")]
Transport(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Tool execution error: {0}")]
ToolExecution(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Connection error: {0}")]
Connection(String),
#[error("Server not found: {0}")]
ServerNotFound(String),
#[error("Tool not found: {0}")]
ToolNotFound(String),
#[error("Operation timed out: {0}")]
Timeout(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Server not available: {0}")]
ServerUnavailable(String),
#[error("Invalid server state: {0}")]
InvalidState(String),
#[error("Resource not found: {0}")]
ResourceNotFound(String),
#[error("Prompt not found: {0}")]
PromptNotFound(String),
#[error("Protocol version mismatch: {0}")]
ProtocolMismatch(String),
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Server capacity exceeded: {0}")]
CapacityExceeded(String),
#[error("MCP error: {0}")]
Generic(String),
}
pub type McpResult<T> = Result<T, McpError>;
impl McpError {
pub fn transport<S: Into<String>>(message: S) -> Self {
Self::Transport(message.into())
}
pub fn configuration<S: Into<String>>(message: S) -> Self {
Self::Configuration(message.into())
}
pub fn tool_execution<S: Into<String>>(message: S) -> Self {
Self::ToolExecution(message.into())
}
pub fn permission_denied<S: Into<String>>(message: S) -> Self {
Self::PermissionDenied(message.into())
}
pub fn connection<S: Into<String>>(message: S) -> Self {
Self::Connection(message.into())
}
pub fn server_not_found<S: Into<String>>(server_name: S) -> Self {
Self::ServerNotFound(server_name.into())
}
pub fn tool_not_found<S: Into<String>>(tool_name: S) -> Self {
Self::ToolNotFound(tool_name.into())
}
pub fn timeout<S: Into<String>>(message: S) -> Self {
Self::Timeout(message.into())
}
pub fn server_unavailable<S: Into<String>>(server_name: S) -> Self {
Self::ServerUnavailable(server_name.into())
}
pub fn invalid_state<S: Into<String>>(message: S) -> Self {
Self::InvalidState(message.into())
}
pub fn resource_not_found<S: Into<String>>(resource_uri: S) -> Self {
Self::ResourceNotFound(resource_uri.into())
}
pub fn prompt_not_found<S: Into<String>>(prompt_name: S) -> Self {
Self::PromptNotFound(prompt_name.into())
}
pub fn protocol_mismatch<S: Into<String>>(message: S) -> Self {
Self::ProtocolMismatch(message.into())
}
pub fn authentication_failed<S: Into<String>>(message: S) -> Self {
Self::AuthenticationFailed(message.into())
}
pub fn rate_limit_exceeded<S: Into<String>>(message: S) -> Self {
Self::RateLimitExceeded(message.into())
}
pub fn capacity_exceeded<S: Into<String>>(message: S) -> Self {
Self::CapacityExceeded(message.into())
}
pub fn generic<S: Into<String>>(message: S) -> Self {
Self::Generic(message.into())
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
McpError::Transport(_)
| McpError::Connection(_)
| McpError::Timeout(_)
| McpError::ServerUnavailable(_)
| McpError::RateLimitExceeded(_)
| McpError::CapacityExceeded(_)
)
}
pub fn is_client_error(&self) -> bool {
matches!(
self,
McpError::Configuration(_)
| McpError::PermissionDenied(_)
| McpError::ServerNotFound(_)
| McpError::ToolNotFound(_)
| McpError::ResourceNotFound(_)
| McpError::PromptNotFound(_)
| McpError::AuthenticationFailed(_)
| McpError::Serialization(_)
)
}
pub fn is_server_error(&self) -> bool {
matches!(
self,
McpError::ToolExecution(_)
| McpError::ServerUnavailable(_)
| McpError::InvalidState(_)
| McpError::CapacityExceeded(_)
| McpError::Generic(_)
)
}
pub fn category(&self) -> &'static str {
match self {
#[cfg(feature = "mcp")]
McpError::Client(_) => "client",
McpError::Transport(_) => "transport",
McpError::Configuration(_) => "configuration",
McpError::ToolExecution(_) => "tool_execution",
McpError::PermissionDenied(_) => "permission",
McpError::Connection(_) => "connection",
McpError::ServerNotFound(_) => "server_not_found",
McpError::ToolNotFound(_) => "tool_not_found",
McpError::Timeout(_) => "timeout",
McpError::Serialization(_) => "serialization",
McpError::Io(_) => "io",
McpError::ServerUnavailable(_) => "server_unavailable",
McpError::InvalidState(_) => "invalid_state",
McpError::ResourceNotFound(_) => "resource_not_found",
McpError::PromptNotFound(_) => "prompt_not_found",
McpError::ProtocolMismatch(_) => "protocol_mismatch",
McpError::AuthenticationFailed(_) => "authentication",
McpError::RateLimitExceeded(_) => "rate_limit",
McpError::CapacityExceeded(_) => "capacity",
McpError::Generic(_) => "generic",
}
}
}
impl From<crate::tools::ToolError> for McpError {
fn from(error: crate::tools::ToolError) -> Self {
match error {
crate::tools::ToolError::NotFound(msg) => McpError::ToolNotFound(msg),
crate::tools::ToolError::PermissionDenied(msg) => McpError::PermissionDenied(msg),
crate::tools::ToolError::ExecutionFailed(msg) => McpError::ToolExecution(msg),
crate::tools::ToolError::InvalidParameters(msg) => McpError::Configuration(msg),
crate::tools::ToolError::Timeout(msg) => McpError::Timeout(msg),
crate::tools::ToolError::Unavailable(msg) => McpError::ServerUnavailable(msg),
crate::tools::ToolError::SecurityViolation(msg) => McpError::PermissionDenied(msg),
crate::tools::ToolError::Io(err) => McpError::Io(err),
crate::tools::ToolError::Json(err) => McpError::Serialization(err),
crate::tools::ToolError::Git(err) => McpError::ToolExecution(err.to_string()),
}
}
}
impl From<McpError> for crate::tools::ToolError {
fn from(error: McpError) -> Self {
match error {
McpError::ToolNotFound(msg) => crate::tools::ToolError::NotFound(msg),
McpError::PermissionDenied(msg) => crate::tools::ToolError::PermissionDenied(msg),
McpError::ToolExecution(msg) => crate::tools::ToolError::ExecutionFailed(msg),
McpError::Configuration(msg) => crate::tools::ToolError::InvalidParameters(msg),
McpError::Timeout(msg) => crate::tools::ToolError::Timeout(msg),
McpError::ServerUnavailable(msg) => crate::tools::ToolError::Unavailable(msg),
McpError::Serialization(err) => crate::tools::ToolError::Json(err),
McpError::Io(err) => crate::tools::ToolError::Io(err),
_ => crate::tools::ToolError::ExecutionFailed(error.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let error = McpError::server_not_found("test-server");
assert_eq!(error.to_string(), "Server not found: test-server");
assert_eq!(error.category(), "server_not_found");
}
#[test]
fn test_error_retryable() {
assert!(McpError::timeout("test").is_retryable());
assert!(McpError::connection("test").is_retryable());
assert!(!McpError::configuration("test").is_retryable());
assert!(!McpError::permission_denied("test").is_retryable());
}
#[test]
fn test_error_categories() {
assert!(McpError::configuration("test").is_client_error());
assert!(McpError::tool_execution("test").is_server_error());
assert!(!McpError::timeout("test").is_client_error());
assert!(!McpError::timeout("test").is_server_error());
}
}