UNPKG

@solana8800/sun_ecommerce_mcp

Version:

Model Context Protocol server for Sun eCommerce Platform (Pure JavaScript) - enables AI assistants to understand and control the complete eCommerce system

902 lines (746 loc) 22.8 kB
# Troubleshooting Guide - Sun eCommerce MCP Server Hướng dẫn xử lý các sự cố thường gặp khi sử dụng Sun eCommerce MCP Server. ## 🔧 Common Issues ### Connection Issues #### Problem: Connection Timeout ``` Error: Request timeout after 30000ms Code: TIMEOUT_ERROR ``` **Possible Causes:** - Network connectivity issues - Server overload - Firewall blocking requests - DNS resolution problems **Solutions:** ```typescript // 1. Increase timeout const client = new SunEcommerceClient({ baseURL: process.env.SUN_ECOMMERCE_API_URL, timeout: 60000, // Increase to 60 seconds retries: 3 }); // 2. Implement exponential backoff async function makeRequestWithBackoff(operation, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { if (error.code === 'TIMEOUT_ERROR' && attempt < maxRetries) { const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s await new Promise(resolve => setTimeout(resolve, delay)); continue; } throw error; } } } // 3. Check network connectivity async function checkConnectivity() { try { const response = await fetch(`${baseURL}/health`, { method: 'GET', timeout: 5000 }); return response.ok; } catch (error) { console.error('Connectivity check failed:', error.message); return false; } } ``` #### Problem: SSL/TLS Certificate Errors ``` Error: unable to verify the first certificate Code: CERT_UNTRUSTED ``` **Solutions:** ```typescript // For development only - DO NOT use in production process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // Better solution: Configure proper certificates const https = require('https'); const fs = require('fs'); const agent = new https.Agent({ ca: fs.readFileSync('path/to/ca-certificate.pem'), cert: fs.readFileSync('path/to/client-certificate.pem'), key: fs.readFileSync('path/to/client-key.pem') }); const client = new SunEcommerceClient({ baseURL: process.env.SUN_ECOMMERCE_API_URL, httpsAgent: agent }); ``` ### Authentication Issues #### Problem: Invalid Authentication Token ``` Error: Authentication failed Code: AUTH_INVALID_TOKEN Status: 401 ``` **Diagnostic Steps:** ```typescript // 1. Verify token format function validateTokenFormat(token) { if (!token) { throw new Error('Token is missing'); } if (!token.startsWith('sun_')) { throw new Error('Invalid token format - should start with "sun_"'); } if (token.length < 32) { throw new Error('Token too short - minimum 32 characters'); } return true; } // 2. Test token validity async function testTokenValidity(token) { try { const response = await fetch(`${baseURL}/auth/verify`, { headers: { 'Authorization': `Bearer ${token}` } }); if (response.status === 401) { throw new Error('Token is invalid or expired'); } if (response.status === 403) { throw new Error('Token lacks required permissions'); } return response.ok; } catch (error) { console.error('Token validation failed:', error.message); return false; } } // 3. Check token expiration function checkTokenExpiration(token) { try { // Decode JWT token (if applicable) const payload = JSON.parse(atob(token.split('.')[1])); const expirationTime = payload.exp * 1000; const currentTime = Date.now(); if (currentTime >= expirationTime) { throw new Error('Token has expired'); } const timeUntilExpiration = expirationTime - currentTime; console.log(`Token expires in ${Math.floor(timeUntilExpiration / 1000 / 60)} minutes`); return true; } catch (error) { console.error('Token expiration check failed:', error.message); return false; } } ``` **Solutions:** ```typescript // 1. Refresh expired token class TokenManager { private token: string; private refreshToken: string; private expiresAt: Date; async getValidToken(): Promise<string> { if (this.isTokenExpired()) { await this.refreshAccessToken(); } return this.token; } private isTokenExpired(): boolean { return this.expiresAt && new Date() > this.expiresAt; } private async refreshAccessToken(): Promise<void> { try { const response = await fetch(`${baseURL}/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: this.refreshToken }) }); if (!response.ok) { throw new Error('Token refresh failed'); } const data = await response.json(); this.token = data.access_token; this.expiresAt = new Date(Date.now() + data.expires_in * 1000); } catch (error) { console.error('Token refresh failed:', error); throw new Error('Authentication required - please login again'); } } } ``` ### API Response Issues #### Problem: Malformed JSON Response ``` Error: Unexpected token < in JSON at position 0 Code: JSON_PARSE_ERROR ``` **Diagnostic Steps:** ```typescript // 1. Check response content type async function debugResponse(url, options) { const response = await fetch(url, options); console.log('Response Status:', response.status); console.log('Response Headers:', Object.fromEntries(response.headers)); const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const text = await response.text(); console.log('Non-JSON Response:', text); throw new Error(`Expected JSON but received ${contentType}: ${text}`); } try { return await response.json(); } catch (error) { const text = await response.text(); console.error('JSON Parse Error. Raw response:', text); throw new Error(`Invalid JSON response: ${text}`); } } // 2. Handle different response types async function safeApiCall(url, options) { try { const response = await fetch(url, options); // Handle different status codes switch (response.status) { case 200: case 201: return await response.json(); case 204: return null; // No content case 400: const errorData = await response.json(); throw new ValidationError(errorData.message, errorData.errors); case 401: throw new AuthenticationError('Authentication required'); case 403: throw new AuthorizationError('Insufficient permissions'); case 404: throw new NotFoundError('Resource not found'); case 429: const retryAfter = response.headers.get('retry-after'); throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`); case 500: case 502: case 503: case 504: throw new ServerError(`Server error: ${response.status}`); default: throw new Error(`Unexpected response status: ${response.status}`); } } catch (error) { if (error instanceof TypeError && error.message.includes('fetch')) { throw new NetworkError('Network request failed'); } throw error; } } ``` ### Data Validation Issues #### Problem: Product Creation Fails ``` Error: Validation failed Code: VALIDATION_ERROR Details: { "sku": ["SKU already exists"], "price": ["Price must be greater than 0"] } ``` **Solutions:** ```typescript // 1. Pre-validate data before API call const productValidationSchema = { name: { required: true, type: 'string', minLength: 1, maxLength: 255 }, sku: { required: true, type: 'string', pattern: /^[A-Z0-9-_]+$/, maxLength: 50 }, price: { required: true, type: 'number', minimum: 0.01 }, description: { type: 'string', maxLength: 5000 } }; function validateProductData(data) { const errors = []; for (const [field, rules] of Object.entries(productValidationSchema)) { const value = data[field]; // Check required fields if (rules.required && (value === undefined || value === null || value === '')) { errors.push(`${field} is required`); continue; } // Skip validation if field is optional and empty if (!rules.required && (value === undefined || value === null || value === '')) { continue; } // Type validation if (rules.type === 'string' && typeof value !== 'string') { errors.push(`${field} must be a string`); } if (rules.type === 'number' && typeof value !== 'number') { errors.push(`${field} must be a number`); } // Length validation if (rules.minLength && value.length < rules.minLength) { errors.push(`${field} must be at least ${rules.minLength} characters`); } if (rules.maxLength && value.length > rules.maxLength) { errors.push(`${field} must be no more than ${rules.maxLength} characters`); } // Pattern validation if (rules.pattern && !rules.pattern.test(value)) { errors.push(`${field} format is invalid`); } // Numeric validation if (rules.minimum && value < rules.minimum) { errors.push(`${field} must be at least ${rules.minimum}`); } } if (errors.length > 0) { throw new ValidationError('Validation failed', errors); } return true; } // 2. Handle duplicate SKU errors async function createProductWithUniqueSkU(productData) { try { validateProductData(productData); return await apiClient.createProduct(productData); } catch (error) { if (error.code === 'VALIDATION_ERROR' && error.details?.sku?.includes('SKU already exists')) { // Generate new SKU const timestamp = Date.now().toString().slice(-6); const newSku = `${productData.sku}-${timestamp}`; console.warn(`SKU ${productData.sku} already exists, trying ${newSku}`); return createProductWithUniqueSkU({ ...productData, sku: newSku }); } throw error; } } ``` ### Performance Issues #### Problem: Slow API Responses ``` Warning: API response time > 5000ms Operation: searchProducts Duration: 8234ms ``` **Diagnostic Steps:** ```typescript // 1. Measure and log performance class PerformanceMonitor { async measureApiCall(operation, apiCall) { const startTime = Date.now(); try { const result = await apiCall(); const duration = Date.now() - startTime; // Log slow operations if (duration > 5000) { console.warn(`Slow API call detected`, { operation, duration, timestamp: new Date().toISOString() }); } // Track metrics this.recordMetric(`api.${operation}.duration`, duration); this.recordMetric(`api.${operation}.success`, 1); return result; } catch (error) { const duration = Date.now() - startTime; this.recordMetric(`api.${operation}.error`, 1); this.recordMetric(`api.${operation}.duration`, duration); throw error; } } } // 2. Optimize search queries function optimizeSearchQuery(query) { return { ...query, // Limit results limit: Math.min(query.limit || 20, 100), // Only fetch necessary fields fields: query.fields || ['id', 'name', 'price', 'image_url'], // Use indexes sort_by: query.sort_by || 'created_at', // Add filters to reduce dataset filters: { ...query.filters, status: 'active' // Only search active products } }; } // 3. Implement caching class CachedApiClient { private cache = new Map(); private cacheTTL = 5 * 60 * 1000; // 5 minutes async searchProducts(query) { const cacheKey = JSON.stringify(query); const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheTTL) { console.log('Cache hit for search query'); return cached.data; } const result = await apiClient.searchProducts(optimizeSearchQuery(query)); this.cache.set(cacheKey, { data: result, timestamp: Date.now() }); return result; } } ``` ### Rate Limiting Issues #### Problem: Rate Limit Exceeded ``` Error: Rate limit exceeded Code: RATE_LIMIT_EXCEEDED Status: 429 Retry-After: 60 ``` **Solutions:** ```typescript // 1. Implement rate limiting with queue class RateLimitedClient { private queue: Array<() => Promise<any>> = []; private processing = false; private requestsPerSecond = 10; private lastRequestTime = 0; async makeRequest(apiCall) { return new Promise((resolve, reject) => { this.queue.push(async () => { try { const result = await apiCall(); resolve(result); } catch (error) { reject(error); } }); this.processQueue(); }); } private async processQueue() { if (this.processing || this.queue.length === 0) { return; } this.processing = true; while (this.queue.length > 0) { const now = Date.now(); const timeSinceLastRequest = now - this.lastRequestTime; const minInterval = 1000 / this.requestsPerSecond; if (timeSinceLastRequest < minInterval) { await new Promise(resolve => setTimeout(resolve, minInterval - timeSinceLastRequest) ); } const request = this.queue.shift(); this.lastRequestTime = Date.now(); try { await request(); } catch (error) { if (error.status === 429) { // Re-queue the request this.queue.unshift(request); // Wait for retry-after period const retryAfter = parseInt(error.headers['retry-after'] || '60'); console.log(`Rate limited, waiting ${retryAfter} seconds`); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); } } } this.processing = false; } } // 2. Batch operations to reduce API calls class BatchProcessor { private batchSize = 10; private batchDelay = 1000; async batchCreateProducts(products) { const results = []; for (let i = 0; i < products.length; i += this.batchSize) { const batch = products.slice(i, i + this.batchSize); console.log(`Processing batch ${Math.floor(i / this.batchSize) + 1}/${Math.ceil(products.length / this.batchSize)}`); const batchPromises = batch.map(product => rateLimitedClient.makeRequest(() => apiClient.createProduct(product)) ); const batchResults = await Promise.allSettled(batchPromises); results.push(...batchResults); // Delay between batches if (i + this.batchSize < products.length) { await new Promise(resolve => setTimeout(resolve, this.batchDelay)); } } return results; } } ``` ## 🔍 Debugging Tools ### Debug Mode ```typescript // Enable debug logging const client = new SunEcommerceClient({ baseURL: process.env.SUN_ECOMMERCE_API_URL, debug: true, // Enable debug mode logLevel: 'debug' }); // Custom debug logger class DebugLogger { static logRequest(method, url, headers, body) { if (process.env.DEBUG_API === 'true') { console.log('🔵 API Request:', { method, url, headers: this.sanitizeHeaders(headers), body: this.sanitizeBody(body) }); } } static logResponse(status, headers, body, duration) { if (process.env.DEBUG_API === 'true') { console.log('🟢 API Response:', { status, headers: Object.fromEntries(headers), body: this.truncateBody(body), duration: `${duration}ms` }); } } private static sanitizeHeaders(headers) { const sanitized = { ...headers }; if (sanitized.authorization) { sanitized.authorization = 'Bearer ***'; } return sanitized; } private static sanitizeBody(body) { if (typeof body === 'string') { try { const parsed = JSON.parse(body); return this.sanitizeObject(parsed); } catch { return body.length > 1000 ? body.substring(0, 1000) + '...' : body; } } return this.sanitizeObject(body); } private static sanitizeObject(obj) { if (!obj || typeof obj !== 'object') return obj; const sanitized = { ...obj }; ['password', 'token', 'secret', 'key'].forEach(field => { if (sanitized[field]) { sanitized[field] = '***'; } }); return sanitized; } private static truncateBody(body) { const str = typeof body === 'string' ? body : JSON.stringify(body); return str.length > 2000 ? str.substring(0, 2000) + '...' : str; } } ``` ### Health Check Utility ```typescript // Comprehensive health check class HealthChecker { async runDiagnostics() { console.log('🔍 Running Sun eCommerce API Diagnostics...'); const results = { connectivity: await this.checkConnectivity(), authentication: await this.checkAuthentication(), apiEndpoints: await this.checkApiEndpoints(), performance: await this.checkPerformance() }; this.printDiagnosticReport(results); return results; } private async checkConnectivity() { try { const start = Date.now(); const response = await fetch(`${baseURL}/health`, { timeout: 5000 }); const duration = Date.now() - start; return { status: response.ok ? 'healthy' : 'unhealthy', responseTime: duration, statusCode: response.status }; } catch (error) { return { status: 'error', error: error.message }; } } private async checkAuthentication() { try { const response = await apiClient.getProducts({ limit: 1 }); return { status: response.success ? 'valid' : 'invalid', error: response.success ? null : response.error }; } catch (error) { return { status: 'error', error: error.message }; } } private async checkApiEndpoints() { const endpoints = [ { name: 'getProducts', call: () => apiClient.getProducts({ limit: 1 }) }, { name: 'getCategories', call: () => apiClient.getCategories({ limit: 1 }) }, { name: 'getPricingRules', call: () => apiClient.getPricingRules({ limit: 1 }) } ]; const results = {}; for (const endpoint of endpoints) { try { const start = Date.now(); const result = await endpoint.call(); const duration = Date.now() - start; results[endpoint.name] = { status: result.success ? 'working' : 'error', responseTime: duration, error: result.success ? null : result.error }; } catch (error) { results[endpoint.name] = { status: 'error', error: error.message }; } } return results; } private async checkPerformance() { const tests = [ { name: 'Small Query', test: () => apiClient.getProducts({ limit: 10 }) }, { name: 'Search Query', test: () => apiClient.searchProducts({ query: 'test', limit: 20 }) } ]; const results = {}; for (const test of tests) { const times = []; // Run test 3 times for (let i = 0; i < 3; i++) { try { const start = Date.now(); await test.test(); times.push(Date.now() - start); } catch (error) { times.push(null); } } const validTimes = times.filter(t => t !== null); results[test.name] = { averageTime: validTimes.length > 0 ? Math.round(validTimes.reduce((a, b) => a + b) / validTimes.length) : null, successRate: `${validTimes.length}/3` }; } return results; } private printDiagnosticReport(results) { console.log('\n📊 Diagnostic Report:'); console.log('='.repeat(50)); // Connectivity console.log(`\n🌐 Connectivity: ${results.connectivity.status}`); if (results.connectivity.responseTime) { console.log(` Response Time: ${results.connectivity.responseTime}ms`); } if (results.connectivity.error) { console.log(` Error: ${results.connectivity.error}`); } // Authentication console.log(`\n🔐 Authentication: ${results.authentication.status}`); if (results.authentication.error) { console.log(` Error: ${results.authentication.error}`); } // API Endpoints console.log('\n🔌 API Endpoints:'); for (const [endpoint, result] of Object.entries(results.apiEndpoints)) { console.log(` ${endpoint}: ${result.status} (${result.responseTime}ms)`); if (result.error) { console.log(` Error: ${result.error}`); } } // Performance console.log('\n⚡ Performance:'); for (const [test, result] of Object.entries(results.performance)) { console.log(` ${test}: ${result.averageTime}ms avg (${result.successRate})`); } console.log('\n' + '='.repeat(50)); } } // Usage const healthChecker = new HealthChecker(); healthChecker.runDiagnostics(); ``` ## 📞 Getting Help ### Support Channels 1. **Documentation**: Check the [API Reference](./api-reference.md) and [Integration Guide](./integration-guide.md) 2. **GitHub Issues**: Report bugs and request features 3. **Community Forum**: Ask questions and share solutions 4. **Email Support**: technical-support@sunecommerce.com ### When Reporting Issues Include the following information: ```typescript // Error report template const errorReport = { // Environment environment: process.env.NODE_ENV, nodeVersion: process.version, clientVersion: '1.0.0', // Error details error: { message: error.message, code: error.code, stack: error.stack }, // Request details request: { method: 'POST', endpoint: '/api/products', headers: sanitizeHeaders(headers), body: sanitizeBody(body) }, // Response details (if any) response: { status: response?.status, headers: response?.headers, body: response?.body }, // Timing timestamp: new Date().toISOString(), duration: requestDuration }; ``` ### Emergency Contacts - **Critical Issues**: emergency@sunecommerce.com - **Security Issues**: security@sunecommerce.com - **Phone Support**: +1-800-SUN-ECOM (business hours) --- *Nếu bạn gặp vấn đề không được đề cập trong hướng dẫn này, vui lòng liên hệ với đội ngũ hỗ trợ kỹ thuật.*