use serde::{Deserialize, Serialize};
use std::fmt;
pub type McpResult<T> = Result<T, McpError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl McpError {
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
pub fn with_data(code: i32, message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
code,
message: message.into(),
data: Some(data),
}
}
pub fn parse_error(details: impl Into<String>) -> Self {
Self::new(-32700, format!("Parse error: {}", details.into()))
}
pub fn invalid_request(details: impl Into<String>) -> Self {
Self::new(-32600, format!("Invalid request: {}", details.into()))
}
pub fn method_not_found(method: impl Into<String>) -> Self {
Self::new(-32601, format!("Method not found: {}", method.into()))
}
pub fn invalid_params(details: impl Into<String>) -> Self {
Self::new(-32602, format!("Invalid params: {}", details.into()))
}
pub fn internal_error(details: impl Into<String>) -> Self {
Self::new(-32603, format!("Internal error: {}", details.into()))
}
pub fn unknown_tool(tool_name: impl Into<String>) -> Self {
let name = tool_name.into();
Self::with_data(
-32001,
format!("Unknown tool: '{}' is not in Jarvy's tool registry", name),
serde_json::json!({ "tool": name }),
)
}
pub fn tool_denied(tool_name: impl Into<String>) -> Self {
let name = tool_name.into();
Self::with_data(
-32002,
format!(
"Tool denied: '{}' is in the MCP denylist and cannot be installed",
name
),
serde_json::json!({ "tool": name }),
)
}
pub fn tool_not_allowed(tool_name: impl Into<String>) -> Self {
let name = tool_name.into();
Self::with_data(
-32003,
format!("Tool not allowed: '{}' is not in the MCP allowlist", name),
serde_json::json!({ "tool": name }),
)
}
pub fn rate_limited(details: impl Into<String>) -> Self {
Self::new(-32004, format!("Rate limited: {}", details.into()))
}
pub fn user_cancelled() -> Self {
Self::new(-32005, "User cancelled the operation")
}
pub fn installation_failed(tool_name: impl Into<String>, reason: impl Into<String>) -> Self {
let name = tool_name.into();
Self::with_data(
-32006,
format!("Installation failed for '{}': {}", name, reason.into()),
serde_json::json!({ "tool": name }),
)
}
pub fn config_error(details: impl Into<String>) -> Self {
Self::new(-32007, format!("Configuration error: {}", details.into()))
}
}
impl fmt::Display for McpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for McpError {}
impl From<std::io::Error> for McpError {
fn from(err: std::io::Error) -> Self {
McpError::internal_error(err.to_string())
}
}
impl From<serde_json::Error> for McpError {
fn from(err: serde_json::Error) -> Self {
McpError::parse_error(err.to_string())
}
}
impl From<toml::de::Error> for McpError {
fn from(err: toml::de::Error) -> Self {
McpError::config_error(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_codes() {
assert_eq!(McpError::parse_error("test").code, -32700);
assert_eq!(McpError::invalid_request("test").code, -32600);
assert_eq!(McpError::method_not_found("test").code, -32601);
assert_eq!(McpError::invalid_params("test").code, -32602);
assert_eq!(McpError::internal_error("test").code, -32603);
assert_eq!(McpError::unknown_tool("git").code, -32001);
assert_eq!(McpError::tool_denied("brew").code, -32002);
assert_eq!(McpError::tool_not_allowed("vim").code, -32003);
assert_eq!(McpError::rate_limited("too fast").code, -32004);
assert_eq!(McpError::user_cancelled().code, -32005);
assert_eq!(McpError::installation_failed("git", "error").code, -32006);
assert_eq!(McpError::config_error("bad config").code, -32007);
}
#[test]
fn test_error_display() {
let err = McpError::unknown_tool("foobar");
assert!(err.to_string().contains("-32001"));
assert!(err.to_string().contains("foobar"));
}
#[test]
fn test_error_with_data() {
let err = McpError::unknown_tool("mytools");
assert!(err.data.is_some());
let data = err.data.unwrap();
assert_eq!(data["tool"], "mytools");
}
}