use std::{io, result::Result as StdResult};
use serde_json::Value;
use thiserror::Error;
use crate::schema::{
CallToolResult, ErrorObject, INVALID_PARAMS, INVALID_REQUEST, JSONRPC_VERSION,
JSONRPCErrorResponse, METHOD_NOT_FOUND, PARSE_ERROR, RequestId,
};
#[derive(Error, Debug, Clone)]
pub enum Error {
#[error("IO error: {message}")]
Io {
message: String,
},
#[error("JSON serialization error: {message}")]
JsonParse {
message: String,
},
#[error("Transport error: {0}")]
Transport(String),
#[error("Transport disconnected unexpectedly")]
TransportDisconnected,
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Invalid request: {0}")]
InvalidRequest(String),
#[error("Method not found: {0}")]
MethodNotFound(String),
#[error("Invalid parameters: {0}")]
InvalidParams(String),
#[error("Internal error: {0}")]
InternalError(String),
#[error("Connection closed")]
ConnectionClosed,
#[error("Resource not found: {uri}")]
ResourceNotFound {
uri: String,
},
#[error("Tool execution failed for '{tool}': {message}")]
ToolExecutionFailed {
tool: String,
message: String,
},
#[error("Invalid message format: {message}")]
InvalidMessageFormat {
message: String,
},
#[error("Tool not found: {0}")]
ToolNotFound(String),
#[error("Prompt not found: {0}")]
PromptNotFound(String),
#[error("Tool group not found: {0}")]
GroupNotFound(String),
#[error("Tool group '{group}' requires active parent '{parent}'")]
GroupInactive {
group: String,
parent: String,
},
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Authorization failed: {0}")]
AuthorizationFailed(String),
#[error("Request timed out after {timeout_ms}ms: {request_id}")]
Timeout {
request_id: String,
timeout_ms: u64,
},
#[error("Request cancelled: {request_id}")]
Cancelled {
request_id: String,
},
}
#[derive(Debug, Clone, Error)]
#[error("{code}: {message}")]
pub struct ToolError {
pub code: &'static str,
pub message: String,
pub structured: Option<Value>,
}
pub const TOOL_ERROR_INVALID_INPUT: &str = "INVALID_INPUT";
pub const TOOL_ERROR_NOT_FOUND: &str = "NOT_FOUND";
pub const TOOL_ERROR_TIMEOUT: &str = "TIMEOUT";
pub const TOOL_ERROR_INTERNAL: &str = "INTERNAL";
impl ToolError {
pub fn new(code: &'static str, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
structured: None,
}
}
pub fn with_structured(mut self, structured: Value) -> Self {
self.structured = Some(structured);
self
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::new(TOOL_ERROR_INVALID_INPUT, message)
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::new(TOOL_ERROR_NOT_FOUND, message)
}
pub fn timeout(message: impl Into<String>) -> Self {
Self::new(TOOL_ERROR_TIMEOUT, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new(TOOL_ERROR_INTERNAL, message)
}
}
impl From<ToolError> for CallToolResult {
fn from(err: ToolError) -> Self {
let mut result = Self::error(err.code, err.message);
if let Some(structured) = err.structured {
result = result.with_structured_content(structured);
}
result
}
}
impl Error {
pub fn tool_execution_failed(tool: impl Into<String>, message: impl Into<String>) -> Self {
Self::ToolExecutionFailed {
tool: tool.into(),
message: message.into(),
}
}
pub(crate) fn to_jsonrpc_response(
&self,
request_id: RequestId,
) -> Option<JSONRPCErrorResponse> {
let (code, message) = match self {
Self::ToolNotFound(tool_name) => {
(METHOD_NOT_FOUND, format!("Tool not found: {tool_name}"))
}
Self::PromptNotFound(prompt_name) => {
(METHOD_NOT_FOUND, format!("Prompt not found: {prompt_name}"))
}
Self::MethodNotFound(method_name) => {
(METHOD_NOT_FOUND, format!("Method not found: {method_name}"))
}
Self::InvalidParams(message) => {
(INVALID_PARAMS, format!("Invalid parameters: {message}"))
}
Self::InvalidRequest(msg) => (INVALID_REQUEST, format!("Invalid request: {msg}")),
Self::GroupNotFound(group) => (INVALID_PARAMS, format!("Group not found: {group}")),
Self::GroupInactive { group, parent } => (
INVALID_PARAMS,
format!("Group '{group}' requires active parent '{parent}'"),
),
Self::JsonParse { message } => {
(PARSE_ERROR, format!("JSON serialization error: {message}"))
}
Self::InvalidMessageFormat { message } => {
(PARSE_ERROR, format!("Invalid message format: {message}"))
}
_ => return None,
};
Some(JSONRPCErrorResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id: Some(request_id),
error: ErrorObject {
code,
message,
data: None,
},
})
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Self::Io {
message: err.to_string(),
}
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::JsonParse {
message: err.to_string(),
}
}
}
pub type Result<T> = StdResult<T, Error>;
pub type ToolResult<T = CallToolResult> = StdResult<T, ToolError>;