use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct McpError {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
pub retryable: bool,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
SessionNotFound,
AmbiguousSessionPrefix,
InvalidSessionId,
InvalidEventIndex,
InvalidCursor,
InvalidParameter,
SearchTimeout,
InternalError,
}
impl McpError {
#[allow(dead_code)]
pub fn session_not_found(session_id: &str) -> Self {
Self {
code: ErrorCode::SessionNotFound,
message: format!("Session not found: {}", session_id),
details: Some(serde_json::json!({ "session_id": session_id })),
retryable: false,
}
}
#[allow(dead_code)]
pub fn ambiguous_prefix(prefix: &str, matches: Vec<String>) -> Self {
Self {
code: ErrorCode::AmbiguousSessionPrefix,
message: format!(
"Session ID prefix '{}' matches {} sessions. Provide more characters.",
prefix,
matches.len()
),
details: Some(serde_json::json!({
"prefix": prefix,
"matches": matches,
})),
retryable: false,
}
}
pub fn invalid_event_index(session_id: &str, index: usize, max: usize) -> Self {
Self {
code: ErrorCode::InvalidEventIndex,
message: format!(
"Event index {} out of bounds for session {} (max: {})",
index, session_id, max
),
details: Some(serde_json::json!({
"session_id": session_id,
"requested_index": index,
"max_index": max,
})),
retryable: false,
}
}
#[allow(dead_code)]
pub fn invalid_cursor(cursor: &str) -> Self {
Self {
code: ErrorCode::InvalidCursor,
message: "Invalid or expired pagination cursor".to_string(),
details: Some(serde_json::json!({ "cursor": cursor })),
retryable: false,
}
}
#[allow(dead_code)]
pub fn invalid_parameter(param_name: &str, reason: &str) -> Self {
Self {
code: ErrorCode::InvalidParameter,
message: format!("Invalid parameter '{}': {}", param_name, reason),
details: Some(serde_json::json!({
"parameter": param_name,
"reason": reason,
})),
retryable: false,
}
}
pub fn internal_error(message: String) -> Self {
Self {
code: ErrorCode::InternalError,
message,
details: None,
retryable: true,
}
}
}
impl From<crate::Error> for McpError {
fn from(err: crate::Error) -> Self {
Self::internal_error(err.to_string())
}
}
impl std::fmt::Display for McpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for McpError {}