Skip to main content
Glama
pixxelboy
by pixxelboy
http.ts5.1 kB
/** * HTTP Client Wrapper for IRCAM Amplify API */ import { MCPError, ErrorCode } from '../types/mcp-tools.js'; import { IRCAM_API_CONFIG } from '../types/ircam-api.js'; import { createAuthHeaders } from './auth.js'; import { formatError } from './errors.js'; /** * HTTP request options */ export interface HttpRequestOptions { /** Request timeout in milliseconds */ timeout?: number; /** Number of retries on transient failures */ retries?: number; /** Custom headers to merge with auth headers */ headers?: Record<string, string>; } /** * HTTP response wrapper */ export interface HttpResponse<T> { ok: boolean; status: number; data?: T; error?: MCPError; } /** * Default request options */ const DEFAULT_OPTIONS: Required<HttpRequestOptions> = { timeout: IRCAM_API_CONFIG.DEFAULT_TIMEOUT, retries: IRCAM_API_CONFIG.MAX_RETRIES, headers: {}, }; /** * Sleep helper for retry delays */ function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Check if an error is retryable */ function isRetryableError(status: number): boolean { return status === 429 || status === 502 || status === 503 || status === 504; } /** * Map HTTP status to error code */ function httpStatusToErrorCode(status: number): ErrorCode { switch (status) { case 401: return 'INVALID_API_KEY'; case 400: return 'INVALID_URL'; case 413: return 'FILE_TOO_LARGE'; case 429: return 'RATE_LIMITED'; case 404: return 'JOB_NOT_FOUND'; case 500: return 'JOB_FAILED'; case 502: case 503: case 504: return 'SERVICE_UNAVAILABLE'; default: return 'UNKNOWN_ERROR'; } } /** * Execute HTTP request with timeout, retries, and error handling */ export async function httpRequest<T>( url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', body?: unknown, options: HttpRequestOptions = {} ): Promise<HttpResponse<T>> { const opts = { ...DEFAULT_OPTIONS, ...options }; const headers = { ...createAuthHeaders(), ...opts.headers, }; let lastError: MCPError | undefined; let retryAfter = 0; for (let attempt = 0; attempt <= opts.retries; attempt++) { // Wait before retry (with exponential backoff or Retry-After) if (attempt > 0) { const delay = retryAfter > 0 ? retryAfter * 1000 : Math.pow(2, attempt) * 1000; await sleep(delay); } try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), opts.timeout); const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); // Check for Retry-After header const retryAfterHeader = response.headers.get('Retry-After'); if (retryAfterHeader) { retryAfter = parseInt(retryAfterHeader, 10) || 0; } // Success if (response.ok) { const data = (await response.json()) as T; return { ok: true, status: response.status, data }; } // Retryable error if (isRetryableError(response.status) && attempt < opts.retries) { lastError = formatError(httpStatusToErrorCode(response.status), undefined, retryAfter); continue; } // Non-retryable error let errorMessage: string | undefined; try { const errorBody = (await response.json()) as { error?: { message?: string } }; errorMessage = errorBody.error?.message; } catch { // Ignore JSON parse errors } return { ok: false, status: response.status, error: formatError(httpStatusToErrorCode(response.status), errorMessage, retryAfter), }; } catch (error) { // Timeout or network error if (error instanceof Error && error.name === 'AbortError') { lastError = formatError('SERVICE_UNAVAILABLE', 'Request timed out'); } else { lastError = formatError('SERVICE_UNAVAILABLE', 'Network error'); } // Retry network errors if (attempt < opts.retries) { continue; } } } // All retries exhausted return { ok: false, status: 0, error: lastError || formatError('UNKNOWN_ERROR'), }; } /** * GET request helper */ export async function httpGet<T>( url: string, options?: HttpRequestOptions ): Promise<HttpResponse<T>> { return httpRequest<T>(url, 'GET', undefined, options); } /** * POST request helper */ export async function httpPost<T>( url: string, body: unknown, options?: HttpRequestOptions ): Promise<HttpResponse<T>> { return httpRequest<T>(url, 'POST', body, options); } /** * Build full API URL */ export function buildApiUrl(endpoint: string, params?: Record<string, string>): string { const url = new URL(endpoint, IRCAM_API_CONFIG.BASE_URL); if (params) { Object.entries(params).forEach(([key, value]) => { url.searchParams.set(key, value); }); } return url.toString(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pixxelboy/amplify-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server