aether-mcp-servers 0.8.9

Built-in MCP tool servers for the Aether AI agent framework
Documentation
//! Error types for coding tools
//!
//! This module provides structured error types for all coding tool operations,
//! replacing the previous `Result<T, String>` pattern with proper `thiserror` enums.

use std::io;
use thiserror::Error;

use crate::lsp::error::LspError;
use crate::workspace_paths::EmptyFilePathError;

pub use crate::file_ops::FileError;

#[doc = include_str!("../docs/coding_error.md")]
#[derive(Debug, Error)]
pub enum CodingError {
    /// File operation errors (read, write, edit)
    #[error(transparent)]
    File(#[from] FileError),

    /// Bash command execution errors
    #[error(transparent)]
    Bash(#[from] BashError),

    /// Web fetch errors
    #[error(transparent)]
    WebFetch(#[from] WebFetchError),

    /// Web search errors
    #[error(transparent)]
    WebSearch(#[from] WebSearchError),

    /// LSP tool errors
    #[error(transparent)]
    Lsp(#[from] LspError),

    /// Tool not configured/available
    #[error("{0} is not configured")]
    NotConfigured(&'static str),

    /// A required file-path argument was empty
    #[error(transparent)]
    EmptyFilePath(#[from] EmptyFilePathError),

    /// An existing file must be read before it may be overwritten
    #[error(
        "Safety check failed: File '{0}' already exists. You must use read_file on it before overwriting. This prevents accidental data loss."
    )]
    NotReadBeforeOverwrite(String),

    /// A file must be read before it may be edited
    #[error(
        "Safety check failed: You must use read_file on '{0}' before editing it. This ensures you understand the current file contents before making changes."
    )]
    NotReadBeforeEdit(String),

    /// Failed to check whether the target file exists
    #[error("Failed to check existence of {path}: {source}")]
    ExistsCheckFailed { path: String, source: io::Error },
}

/// Renders the error as the text content of an errored MCP tool result.
impl rmcp::model::IntoContents for CodingError {
    fn into_contents(self) -> Vec<rmcp::model::ContentBlock> {
        vec![rmcp::model::ContentBlock::text(self.to_string())]
    }
}

/// Errors related to bash command execution
#[derive(Debug, Error)]
pub enum BashError {
    /// Command is forbidden (e.g., rm without flags)
    #[error("{0}")]
    Forbidden(String),

    /// Timeout exceeds maximum allowed
    #[error("Timeout cannot exceed 600000ms (10 minutes)")]
    TimeoutTooLarge,

    /// Failed to spawn process
    #[error("Failed to execute command '{command}': {reason}")]
    SpawnFailed { command: String, reason: String },
}

/// Errors related to web fetch operations
#[derive(Debug, Clone, Error)]
pub enum WebFetchError {
    /// Invalid URL format
    #[error("Invalid URL: {0}")]
    InvalidUrl(#[source] url::ParseError),

    /// HTTP request failed
    #[error("Request failed: {0}")]
    RequestFailed(String),

    /// Request timed out
    #[error("Request timed out after {0}ms")]
    Timeout(u64),
}

/// Errors related to web search operations
#[derive(Debug, Clone, Error)]
pub enum WebSearchError {
    /// Invalid search query
    #[error("Invalid search query: {0}")]
    InvalidQuery(String),

    /// API request failed
    #[error("API request failed: {0}")]
    ApiError(String),

    /// Rate limit exceeded
    #[error("Rate limit exceeded: {message}")]
    RateLimited { message: String, retry_after: Option<std::time::Duration> },

    /// Request timed out
    #[error("Request timed out after {0}ms")]
    Timeout(u64),

    /// Configuration error
    #[error("Configuration error: {0}")]
    ConfigError(String),

    /// Failed to parse API response
    #[error("Failed to parse API response: {0}")]
    ParseError(String),
}