use anyhow::anyhow;
use std::fmt;
pub type McpResult<T> = anyhow::Result<T>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
ToolNotFound = 1,
ToolInvocationFailed = 2,
ProviderNotFound = 11,
ProviderUnavailable = 12,
SchemaInvalid = 21,
ConfigurationError = 31,
InitializationTimeout = 32,
}
impl ErrorCode {
pub fn code(&self) -> String {
format!("MCP_E{:03}", *self as u32)
}
pub fn name(&self) -> &'static str {
match self {
Self::ToolNotFound => "ToolNotFound",
Self::ToolInvocationFailed => "ToolInvocationFailed",
Self::ProviderNotFound => "ProviderNotFound",
Self::ProviderUnavailable => "ProviderUnavailable",
Self::SchemaInvalid => "SchemaInvalid",
Self::ConfigurationError => "ConfigurationError",
Self::InitializationTimeout => "InitializationTimeout",
}
}
pub fn user_guidance(&self) -> &'static str {
match self {
Self::ToolNotFound => {
"Check that the tool name is correct and the MCP provider is running."
}
Self::ToolInvocationFailed => {
"The MCP tool returned an error. Check the tool's arguments and provider logs."
}
Self::ProviderNotFound => {
"Verify the provider name in vtcode.toml or .mcp.json matches a configured MCP server."
}
Self::ProviderUnavailable => {
"The MCP server may be down. Check that the command/endpoint is reachable."
}
Self::SchemaInvalid => {
"The tool's input schema does not match expected format. Check the MCP server implementation."
}
Self::ConfigurationError => {
"Review the MCP section of vtcode.toml or .mcp.json for syntax errors."
}
Self::InitializationTimeout => {
"The MCP server took too long to start. Increase startup_timeout_ms or check the server process."
}
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.code())
}
}
pub fn tool_not_found(name: &str) -> anyhow::Error {
anyhow!(
"[{}] MCP tool '{}' not found",
ErrorCode::ToolNotFound.code(),
name
)
}
pub fn provider_not_found(name: &str) -> anyhow::Error {
anyhow!(
"[{}] MCP provider '{}' not found",
ErrorCode::ProviderNotFound.code(),
name
)
}
pub fn provider_unavailable(name: &str) -> anyhow::Error {
anyhow!(
"[{}] MCP provider '{}' is unavailable or failed to initialize",
ErrorCode::ProviderUnavailable.code(),
name
)
}
pub fn schema_invalid(reason: &str) -> anyhow::Error {
anyhow!(
"[{}] MCP tool schema is invalid: {}",
ErrorCode::SchemaInvalid.code(),
reason
)
}
pub fn tool_invocation_failed(provider: &str, tool: &str, reason: &str) -> anyhow::Error {
anyhow!(
"[{}] Failed to invoke tool '{}' on provider '{}': {}",
ErrorCode::ToolInvocationFailed.code(),
tool,
provider,
reason
)
}
pub fn initialization_timeout(timeout_secs: u64) -> anyhow::Error {
anyhow!(
"[{}] MCP initialization timeout after {} seconds",
ErrorCode::InitializationTimeout.code(),
timeout_secs
)
}
pub fn configuration_error(reason: &str) -> anyhow::Error {
anyhow!(
"[{}] MCP configuration error: {}",
ErrorCode::ConfigurationError.code(),
reason
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_codes_format() {
assert_eq!(ErrorCode::ToolNotFound.code(), "MCP_E001");
assert_eq!(ErrorCode::ToolInvocationFailed.code(), "MCP_E002");
assert_eq!(ErrorCode::ProviderNotFound.code(), "MCP_E011");
assert_eq!(ErrorCode::ProviderUnavailable.code(), "MCP_E012");
assert_eq!(ErrorCode::SchemaInvalid.code(), "MCP_E021");
assert_eq!(ErrorCode::ConfigurationError.code(), "MCP_E031");
assert_eq!(ErrorCode::InitializationTimeout.code(), "MCP_E032");
}
#[test]
fn test_error_names() {
assert_eq!(ErrorCode::ToolNotFound.name(), "ToolNotFound");
assert_eq!(ErrorCode::ProviderNotFound.name(), "ProviderNotFound");
assert_eq!(
ErrorCode::InitializationTimeout.name(),
"InitializationTimeout"
);
}
#[test]
fn test_error_messages_with_codes() {
let err = tool_not_found("missing_tool");
let msg = err.to_string();
assert!(msg.contains("[MCP_E001]"));
assert!(msg.contains("missing_tool"));
assert!(msg.contains("not found"));
let err = provider_not_found("missing_provider");
let msg = err.to_string();
assert!(msg.contains("[MCP_E011]"));
assert!(msg.contains("missing_provider"));
let err = initialization_timeout(15);
let msg = err.to_string();
assert!(msg.contains("[MCP_E032]"));
assert!(msg.contains("15 seconds"));
let err = tool_invocation_failed(
"claude",
vtcode_config::constants::tools::LIST_FILES,
"timeout",
);
let msg = err.to_string();
assert!(msg.contains("[MCP_E002]"));
assert!(msg.contains(vtcode_config::constants::tools::LIST_FILES));
assert!(msg.contains("timeout"));
}
#[test]
fn test_error_code_display() {
assert_eq!(ErrorCode::ToolNotFound.to_string(), "MCP_E001");
assert_eq!(ErrorCode::ProviderUnavailable.to_string(), "MCP_E012");
}
}