1use anyhow::anyhow;
12use std::fmt;
13
14pub type McpResult<T> = anyhow::Result<T>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ErrorCode {
19 ToolNotFound = 1,
21 ToolInvocationFailed = 2,
23 ProviderNotFound = 11,
25 ProviderUnavailable = 12,
27 SchemaInvalid = 21,
29 ConfigurationError = 31,
31 InitializationTimeout = 32,
33}
34
35impl ErrorCode {
36 fn code(&self) -> String {
38 format!("MCP_E{:03}", *self as u32)
39 }
40
41 fn name(&self) -> &'static str {
43 match self {
44 Self::ToolNotFound => "ToolNotFound",
45 Self::ToolInvocationFailed => "ToolInvocationFailed",
46 Self::ProviderNotFound => "ProviderNotFound",
47 Self::ProviderUnavailable => "ProviderUnavailable",
48 Self::SchemaInvalid => "SchemaInvalid",
49 Self::ConfigurationError => "ConfigurationError",
50 Self::InitializationTimeout => "InitializationTimeout",
51 }
52 }
53
54 pub fn user_guidance(&self) -> &'static str {
56 match self {
57 Self::ToolNotFound => "Check that the tool name is correct and the MCP provider is running.",
58 Self::ToolInvocationFailed => {
59 "The MCP tool returned an error. Check the tool's arguments and provider logs."
60 }
61 Self::ProviderNotFound => {
62 "Verify the provider name in vtcode.toml or .mcp.json matches a configured MCP server."
63 }
64 Self::ProviderUnavailable => "The MCP server may be down. Check that the command/endpoint is reachable.",
65 Self::SchemaInvalid => {
66 "The tool's input schema does not match expected format. Check the MCP server implementation."
67 }
68 Self::ConfigurationError => "Review the MCP section of vtcode.toml or .mcp.json for syntax errors.",
69 Self::InitializationTimeout => {
70 "The MCP server took too long to start. Increase startup_timeout_ms or check the server process."
71 }
72 }
73 }
74}
75
76impl fmt::Display for ErrorCode {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}", self.code())
79 }
80}
81
82pub fn tool_not_found(name: &str) -> anyhow::Error {
86 anyhow!("[{}] MCP tool '{}' not found", ErrorCode::ToolNotFound.code(), name)
87}
88
89pub fn provider_not_found(name: &str) -> anyhow::Error {
93 anyhow!("[{}] MCP provider '{}' not found", ErrorCode::ProviderNotFound.code(), name)
94}
95
96pub fn provider_unavailable(name: &str) -> anyhow::Error {
100 anyhow!(
101 "[{}] MCP provider '{}' is unavailable or failed to initialize",
102 ErrorCode::ProviderUnavailable.code(),
103 name
104 )
105}
106
107pub fn schema_invalid(reason: &str) -> anyhow::Error {
111 anyhow!("[{}] MCP tool schema is invalid: {}", ErrorCode::SchemaInvalid.code(), reason)
112}
113
114pub fn tool_invocation_failed(provider: &str, tool: &str, reason: &str) -> anyhow::Error {
118 anyhow!(
119 "[{}] Failed to invoke tool '{}' on provider '{}': {}",
120 ErrorCode::ToolInvocationFailed.code(),
121 tool,
122 provider,
123 reason
124 )
125}
126
127pub fn initialization_timeout(timeout_secs: u64) -> anyhow::Error {
131 anyhow!(
132 "[{}] MCP initialization timeout after {} seconds",
133 ErrorCode::InitializationTimeout.code(),
134 timeout_secs
135 )
136}
137
138pub fn configuration_error(reason: &str) -> anyhow::Error {
142 anyhow!("[{}] MCP configuration error: {}", ErrorCode::ConfigurationError.code(), reason)
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_error_codes_format() {
151 assert_eq!(ErrorCode::ToolNotFound.code(), "MCP_E001");
152 assert_eq!(ErrorCode::ToolInvocationFailed.code(), "MCP_E002");
153 assert_eq!(ErrorCode::ProviderNotFound.code(), "MCP_E011");
154 assert_eq!(ErrorCode::ProviderUnavailable.code(), "MCP_E012");
155 assert_eq!(ErrorCode::SchemaInvalid.code(), "MCP_E021");
156 assert_eq!(ErrorCode::ConfigurationError.code(), "MCP_E031");
157 assert_eq!(ErrorCode::InitializationTimeout.code(), "MCP_E032");
158 }
159
160 #[test]
161 fn test_error_names() {
162 assert_eq!(ErrorCode::ToolNotFound.name(), "ToolNotFound");
163 assert_eq!(ErrorCode::ProviderNotFound.name(), "ProviderNotFound");
164 assert_eq!(ErrorCode::InitializationTimeout.name(), "InitializationTimeout");
165 }
166
167 #[test]
168 fn test_error_messages_with_codes() {
169 let err = tool_not_found("missing_tool");
170 let msg = err.to_string();
171 assert!(msg.contains("[MCP_E001]"));
172 assert!(msg.contains("missing_tool"));
173 assert!(msg.contains("not found"));
174
175 let err = provider_not_found("missing_provider");
176 let msg = err.to_string();
177 assert!(msg.contains("[MCP_E011]"));
178 assert!(msg.contains("missing_provider"));
179
180 let err = initialization_timeout(15);
181 let msg = err.to_string();
182 assert!(msg.contains("[MCP_E032]"));
183 assert!(msg.contains("15 seconds"));
184
185 let err = tool_invocation_failed("claude", vtcode_config::constants::tools::LIST_FILES, "timeout");
186 let msg = err.to_string();
187 assert!(msg.contains("[MCP_E002]"));
188 assert!(msg.contains(vtcode_config::constants::tools::LIST_FILES));
189 assert!(msg.contains("timeout"));
190 }
191
192 #[test]
193 fn test_error_code_display() {
194 assert_eq!(ErrorCode::ToolNotFound.to_string(), "MCP_E001");
195 assert_eq!(ErrorCode::ProviderUnavailable.to_string(), "MCP_E012");
196 }
197}