UNPKG

gitlab-review-mcp

Version:

Node.js MCP server for GitLab code review operations with AI integration support

170 lines 6.26 kB
import axios from 'axios'; export class ApiClient { baseURL; token; timeout; maxRetries; constructor(baseURL, token, timeout = 30000, maxRetries = 3) { this.baseURL = baseURL; this.token = token; this.timeout = timeout; this.maxRetries = maxRetries; } async request(endpoint, options = {}) { const { method = 'GET', data, headers = {}, timeout = this.timeout } = options; const requestHeaders = { 'User-Agent': 'node-code-review-mcp/1.0.0', 'Accept': 'application/json', ...headers, }; if (this.token) { requestHeaders['Authorization'] = `Bearer ${this.token}`; } const config = { method, url: `${this.baseURL}/${endpoint}`, headers: requestHeaders, timeout, data, }; return this.executeWithRetry(async () => { try { const response = await axios(config); return response; } catch (error) { if (this.isAxiosError(error)) { throw this.handleApiError(error); } throw error; } }); } async executeWithRetry(operation) { let lastError; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === this.maxRetries) { break; } // Only retry on specific errors if (this.shouldRetry(error)) { const delay = this.calculateDelay(attempt); console.warn(`Request failed (attempt ${attempt}/${this.maxRetries}), retrying in ${delay}ms...`); await this.sleep(delay); } else { break; } } } throw lastError; } shouldRetry(error) { if (this.isAxiosError(error)) { const status = error.response?.status; // Retry on network errors, timeouts, and 5xx server errors return !status || status >= 500 || status === 408 || status === 429; } // Retry on network/timeout errors return error.code === 'ECONNRESET' || error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT'; } calculateDelay(attempt) { // Exponential backoff with jitter const baseDelay = 1000; // 1 second const maxDelay = 10000; // 10 seconds const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay); // Add jitter (±25%) const jitter = delay * 0.25 * (Math.random() * 2 - 1); return Math.round(delay + jitter); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } isAxiosError(error) { return error.isAxiosError === true; } handleApiError(error) { const status = error.response?.status; const statusText = error.response?.statusText; const data = error.response?.data; let message = `API request failed: ${status} ${statusText}`; if (data && typeof data === 'object') { // GitHub/GitLab error format if ('message' in data) { message += ` - ${data.message}`; } if ('error_description' in data) { message += ` - ${data.error_description}`; } } const apiError = new Error(message); apiError.status = status; apiError.statusText = statusText; apiError.data = data; return apiError; } // Rate limiting support async requestWithRateLimit(endpoint, options = {}) { try { const response = await this.request(endpoint, options); // Check rate limit headers (GitHub format) const remaining = response.headers['x-ratelimit-remaining']; const reset = response.headers['x-ratelimit-reset']; if (remaining && parseInt(remaining) < 10) { console.warn(`API rate limit low: ${remaining} requests remaining`); if (reset) { const resetTime = new Date(parseInt(reset) * 1000); console.warn(`Rate limit resets at: ${resetTime.toISOString()}`); } } return response; } catch (error) { // Handle rate limit exceeded if (this.isAxiosError(error) && error.response?.status === 403) { const resetHeader = error.response.headers['x-ratelimit-reset']; if (resetHeader) { const resetTime = parseInt(resetHeader) * 1000; const waitTime = resetTime - Date.now(); if (waitTime > 0 && waitTime < 3600000) { // Wait max 1 hour console.warn(`Rate limit exceeded. Waiting ${Math.ceil(waitTime / 1000)} seconds...`); await this.sleep(waitTime + 1000); // Add 1 second buffer return this.request(endpoint, options); // Retry once } } } throw error; } } // Health check async healthCheck() { try { // Try a simple request to test connectivity - use a valid GitLab API endpoint // GitLab API v4 has a version endpoint that should always be available await this.request('version', { timeout: 5000 }); return true; } catch (error) { console.warn('API health check failed:', error instanceof Error ? error.message : String(error)); return false; } } // Get API info getApiInfo() { return { baseURL: this.baseURL, hasToken: !!this.token, timeout: this.timeout, maxRetries: this.maxRetries, }; } } //# sourceMappingURL=api-client.js.map