import Foundation #if canImport(System) import System #else @preconcurrency import SystemPackage #endif /// A model context protocol error. public enum MCPError: Swift.Error, Sendable { // Standard JSON-RPC 2.0 errors (-32700 to -32603) case parseError(String?) // -32700 case invalidRequest(String?) // -32600 case methodNotFound(String?) // -32601 case invalidParams(String?) // -32602 case internalError(String?) // -32603 // Server errors (-32000 to -32099) case serverError(code: Int, message: String) // Transport specific errors case connectionClosed case transportError(Swift.Error) /// The JSON-RPC 2.0 error code public var code: Int { switch self { case .parseError: return -32700 case .invalidRequest: return -32600 case .methodNotFound: return -32601 case .invalidParams: return -32602 case .internalError: return -32603 case .serverError(let code, _): return code case .connectionClosed: return -32000 case .transportError: return -32001 } } /// Check if an error represents a "resource temporarily unavailable" condition public static func isResourceTemporarilyUnavailable(_ error: Swift.Error) -> Bool { #if canImport(System) if let errno = error as? System.Errno, errno == .resourceTemporarilyUnavailable { return true } #else if let errno = error as? SystemPackage.Errno, errno == .resourceTemporarilyUnavailable { return true } #endif return false } } // MARK: LocalizedError extension MCPError: LocalizedError { public var errorDescription: String? { switch self { case .parseError(let detail): return "Parse error: Invalid JSON" + (detail.map { ": \($0)" } ?? "") case .invalidRequest(let detail): return "Invalid Request" + (detail.map { ": \($0)" } ?? "") case .methodNotFound(let detail): return "Method not found" + (detail.map { ": \($0)" } ?? "") case .invalidParams(let detail): return "Invalid params" + (detail.map { ": \($0)" } ?? "") case .internalError(let detail): return "Internal error" + (detail.map { ": \($0)" } ?? "") case .serverError(_, let message): return "Server error: \(message)" case .connectionClosed: return "Connection closed" case .transportError(let error): return "Transport error: \(error.localizedDescription)" } } public var failureReason: String? { switch self { case .parseError: return "The server received invalid JSON that could not be parsed" case .invalidRequest: return "The JSON sent is not a valid Request object" case .methodNotFound: return "The method does not exist or is not available" case .invalidParams: return "Invalid method parameter(s)" case .internalError: return "Internal JSON-RPC error" case .serverError: return "Server-defined error occurred" case .connectionClosed: return "The connection to the server was closed" case .transportError(let error): return (error as? LocalizedError)?.failureReason ?? error.localizedDescription } } public var recoverySuggestion: String? { switch self { case .parseError: return "Verify that the JSON being sent is valid and well-formed" case .invalidRequest: return "Ensure the request follows the JSON-RPC 2.0 specification format" case .methodNotFound: return "Check the method name and ensure it is supported by the server" case .invalidParams: return "Verify the parameters match the method's expected parameters" case .connectionClosed: return "Try reconnecting to the server" default: return nil } } } // MARK: CustomDebugStringConvertible extension MCPError: CustomDebugStringConvertible { public var debugDescription: String { switch self { case .transportError(let error): return "[\(code)] \(errorDescription ?? "") (Underlying error: \(String(reflecting: error)))" default: return "[\(code)] \(errorDescription ?? "")" } } } // MARK: Codable extension MCPError: Codable { private enum CodingKeys: String, CodingKey { case code, message, data } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(code, forKey: .code) try container.encode(errorDescription ?? "Unknown error", forKey: .message) // Encode additional data if available switch self { case .parseError(let detail), .invalidRequest(let detail), .methodNotFound(let detail), .invalidParams(let detail), .internalError(let detail): if let detail = detail { try container.encode(["detail": detail], forKey: .data) } case .serverError(_, _): // No additional data for server errors break case .connectionClosed: break case .transportError(let error): try container.encode( ["error": error.localizedDescription], forKey: .data ) } } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let code = try container.decode(Int.self, forKey: .code) let message = try container.decode(String.self, forKey: .message) let data = try container.decodeIfPresent([String: Value].self, forKey: .data) // Helper to extract detail from data, falling back to message if needed let unwrapDetail: (String?) -> String? = { fallback in guard let detailValue = data?["detail"] else { return fallback } if case .string(let str) = detailValue { return str } return fallback } switch code { case -32700: self = .parseError(unwrapDetail(message)) case -32600: self = .invalidRequest(unwrapDetail(message)) case -32601: self = .methodNotFound(unwrapDetail(message)) case -32602: self = .invalidParams(unwrapDetail(message)) case -32603: self = .internalError(unwrapDetail(nil)) case -32000: self = .connectionClosed case -32001: // Extract underlying error string if present let underlyingErrorString = data?["error"].flatMap { val -> String? in if case .string(let str) = val { return str } return nil } ?? message self = .transportError( NSError( domain: "org.jsonrpc.error", code: code, userInfo: [NSLocalizedDescriptionKey: underlyingErrorString] ) ) default: self = .serverError(code: code, message: message) } } } // MARK: Equatable extension MCPError: Equatable { public static func == (lhs: MCPError, rhs: MCPError) -> Bool { lhs.code == rhs.code } } // MARK: Hashable extension MCPError: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(code) switch self { case .parseError(let detail): hasher.combine(detail) case .invalidRequest(let detail): hasher.combine(detail) case .methodNotFound(let detail): hasher.combine(detail) case .invalidParams(let detail): hasher.combine(detail) case .internalError(let detail): hasher.combine(detail) case .serverError(_, let message): hasher.combine(message) case .connectionClosed: break case .transportError(let error): hasher.combine(error.localizedDescription) } } }