use serde::{Deserialize, Serialize};
use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
pub type McpResult<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, thiserror::Error)]
pub struct Error {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
impl Error {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
code,
message: message.into(),
data: Some(data),
}
}
pub fn parse_error(message: impl Into<String>) -> Self {
Self::new(ErrorCode::ParseError, message)
}
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidRequest, message)
}
pub fn method_not_found(method: impl Into<String>) -> Self {
Self::new(
ErrorCode::MethodNotFound,
format!("Method not found: {}", method.into()),
)
}
pub fn invalid_params(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidParams, message)
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InternalError, message)
}
pub fn protocol_version_mismatch(client_version: &str, server_version: &str) -> Self {
Self::with_data(
ErrorCode::InvalidRequest,
format!("Protocol version mismatch: client={client_version}, server={server_version}"),
serde_json::json!({
"client_version": client_version,
"server_version": server_version
}),
)
}
pub fn unauthorized(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Unauthorized, message)
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Forbidden, message)
}
pub fn resource_not_found(resource: impl Into<String>) -> Self {
Self::new(
ErrorCode::ResourceNotFound,
format!("Resource not found: {}", resource.into()),
)
}
pub fn tool_not_found(tool: impl Into<String>) -> Self {
Self::new(
ErrorCode::ToolNotFound,
format!("Tool not found: {}", tool.into()),
)
}
pub fn validation_error(message: impl Into<String>) -> Self {
Self::new(ErrorCode::ValidationError, message)
}
pub fn rate_limit_exceeded(message: impl Into<String>) -> Self {
Self::new(ErrorCode::RateLimitExceeded, message)
}
pub fn url_elicitation_required(
message: impl Into<String>,
elicitations: Vec<crate::UrlElicitationInfo>,
) -> Self {
let data = crate::UrlElicitationRequiredData { elicitations };
Self::with_data(
ErrorCode::UrlElicitationRequired,
message,
serde_json::to_value(data).unwrap_or_default(),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
Unauthorized = -32000,
Forbidden = -32001,
ResourceNotFound = -32002,
ToolNotFound = -32003,
ValidationError = -32004,
RateLimitExceeded = -32005,
UrlElicitationRequired = -32042,
}
impl Serialize for ErrorCode {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32(*self as i32)
}
}
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let code = i32::deserialize(deserializer)?;
match code {
-32700 => Ok(ErrorCode::ParseError),
-32600 => Ok(ErrorCode::InvalidRequest),
-32601 => Ok(ErrorCode::MethodNotFound),
-32602 => Ok(ErrorCode::InvalidParams),
-32603 => Ok(ErrorCode::InternalError),
-32000 => Ok(ErrorCode::Unauthorized),
-32001 => Ok(ErrorCode::Forbidden),
-32002 => Ok(ErrorCode::ResourceNotFound),
-32003 => Ok(ErrorCode::ToolNotFound),
-32004 => Ok(ErrorCode::ValidationError),
-32005 => Ok(ErrorCode::RateLimitExceeded),
-32042 => Ok(ErrorCode::UrlElicitationRequired),
_ => Err(serde::de::Error::custom(format!(
"Unknown error code: {code}"
))),
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
ErrorCode::ParseError => "ParseError",
ErrorCode::InvalidRequest => "InvalidRequest",
ErrorCode::MethodNotFound => "MethodNotFound",
ErrorCode::InvalidParams => "InvalidParams",
ErrorCode::InternalError => "InternalError",
ErrorCode::Unauthorized => "Unauthorized",
ErrorCode::Forbidden => "Forbidden",
ErrorCode::ResourceNotFound => "ResourceNotFound",
ErrorCode::ToolNotFound => "ToolNotFound",
ErrorCode::ValidationError => "ValidationError",
ErrorCode::RateLimitExceeded => "RateLimitExceeded",
ErrorCode::UrlElicitationRequired => "UrlElicitationRequired",
};
write!(f, "{name}")
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::parse_error(err.to_string())
}
}
impl From<uuid::Error> for Error {
fn from(err: uuid::Error) -> Self {
Error::validation_error(format!("Invalid UUID: {err}"))
}
}
impl From<validator::ValidationErrors> for Error {
fn from(err: validator::ValidationErrors) -> Self {
Error::validation_error(err.to_string())
}
}
#[cfg(feature = "logging")]
impl From<pulseengine_logging::LoggingError> for Error {
fn from(err: pulseengine_logging::LoggingError) -> Self {
match err {
pulseengine_logging::LoggingError::Config(msg) => {
Error::invalid_request(format!("Logging config: {msg}"))
}
pulseengine_logging::LoggingError::Io(io_err) => {
Error::internal_error(format!("Logging I/O: {io_err}"))
}
pulseengine_logging::LoggingError::Serialization(serde_err) => {
Error::internal_error(format!("Logging serialization: {serde_err}"))
}
pulseengine_logging::LoggingError::Tracing(msg) => {
Error::internal_error(format!("Tracing: {msg}"))
}
}
}
}
#[cfg(feature = "logging")]
impl pulseengine_logging::ErrorClassification for Error {
fn error_type(&self) -> &str {
match self.code {
ErrorCode::ParseError => "parse_error",
ErrorCode::InvalidRequest => "invalid_request",
ErrorCode::MethodNotFound => "method_not_found",
ErrorCode::InvalidParams => "invalid_params",
ErrorCode::InternalError => "internal_error",
ErrorCode::Unauthorized => "unauthorized",
ErrorCode::Forbidden => "forbidden",
ErrorCode::ResourceNotFound => "resource_not_found",
ErrorCode::ToolNotFound => "tool_not_found",
ErrorCode::ValidationError => "validation_error",
ErrorCode::RateLimitExceeded => "rate_limit_exceeded",
ErrorCode::UrlElicitationRequired => "url_elicitation_required",
}
}
fn is_retryable(&self) -> bool {
matches!(
self.code,
ErrorCode::InternalError
| ErrorCode::RateLimitExceeded
| ErrorCode::UrlElicitationRequired
)
}
fn is_timeout(&self) -> bool {
false }
fn is_auth_error(&self) -> bool {
matches!(self.code, ErrorCode::Unauthorized | ErrorCode::Forbidden)
}
fn is_connection_error(&self) -> bool {
false }
}