Skip to main content
Glama
client.ts7.83 kB
import axios, { AxiosInstance, AxiosError } from 'axios'; import { LiaraApiError } from './types.js'; /** * Configuration for Liara API client */ export interface LiaraClientConfig { apiToken: string; teamId?: string; baseURL?: string; maxRetries?: number; retryDelay?: number; } /** * Liara API client for making authenticated requests */ export class LiaraClient { private client: AxiosInstance; private teamId?: string; private maxRetries: number; private retryDelay: number; constructor(config: LiaraClientConfig) { const baseURL = config.baseURL || process.env.LIARA_API_BASE_URL || 'https://api.iran.liara.ir'; this.client = axios.create({ baseURL, headers: { 'Authorization': `Bearer ${config.apiToken}`, 'Content-Type': 'application/json', }, timeout: 30000, // 30 seconds }); this.teamId = config.teamId; this.maxRetries = config.maxRetries ?? 3; this.retryDelay = config.retryDelay ?? 1000; // Add response interceptor for error handling this.client.interceptors.response.use( (response) => response, (error: AxiosError<LiaraApiError>) => { return Promise.reject(this.handleError(error)); } ); } /** * Sleep for a given number of milliseconds */ private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Execute request with retry logic for rate limiting */ private async executeWithRetry<T>( requestFn: () => Promise<T>, retryCount = 0 ): Promise<T> { try { return await requestFn(); } catch (error: unknown) { const err = error as { statusCode?: number; response?: { status?: number } }; // Retry on rate limiting (429) or server errors (5xx) const statusCode = err.statusCode || err.response?.status; const isRetryable = statusCode === 429 || (statusCode !== undefined && statusCode >= 500 && statusCode < 600); if (isRetryable && retryCount < this.maxRetries) { // Exponential backoff: 1s, 2s, 4s... const delay = this.retryDelay * Math.pow(2, retryCount); await this.sleep(delay); return this.executeWithRetry(requestFn, retryCount + 1); } throw error; } } /** * Handle API errors and convert to user-friendly messages */ private handleError(error: AxiosError<LiaraApiError>): Error { if (error.response) { const status = error.response.status; const data = error.response.data; let message = data?.message || data?.error || 'Unknown API error'; switch (status) { case 401: message = 'Authentication failed. Please check your API token.'; break; case 403: message = 'Access forbidden. You may not have permission for this operation.'; break; case 404: message = data?.message || 'Resource not found.'; break; case 409: message = data?.message || 'Conflict: Resource already exists or operation cannot be completed.'; break; case 429: message = 'Rate limit exceeded. Please try again later.'; break; case 500: case 502: case 503: message = 'Liara API is temporarily unavailable. Please try again later.'; break; } const apiError = new Error(message); (apiError as any).statusCode = status; (apiError as any).originalError = data; return apiError; } else if (error.request) { return new Error('Unable to connect to Liara API. Please check your internet connection.'); } else { return new Error(error.message || 'An unexpected error occurred.'); } } /** * Add team ID parameter to request if configured */ private addTeamId(params: Record<string, unknown> = {}): Record<string, unknown> { if (this.teamId) { return { ...params, teamID: this.teamId }; } return params; } /** * GET request with automatic retry for rate limiting */ async get<T>(url: string, params?: Record<string, string | number | boolean>): Promise<T> { return this.executeWithRetry(async () => { const response = await this.client.get<T>(url, { params: this.addTeamId(params) as Record<string, string | number | boolean> }); return response.data; }); } /** * POST request with automatic retry for rate limiting */ async post<T>(url: string, data?: unknown, params?: Record<string, string | number | boolean>): Promise<T> { return this.executeWithRetry(async () => { const response = await this.client.post<T>(url, data, { params: this.addTeamId(params) as Record<string, string | number | boolean> }); return response.data; }); } /** * PUT request with automatic retry for rate limiting */ async put<T>(url: string, data?: unknown, params?: Record<string, string | number | boolean>): Promise<T> { return this.executeWithRetry(async () => { const response = await this.client.put<T>(url, data, { params: this.addTeamId(params) as Record<string, string | number | boolean> }); return response.data; }); } /** * DELETE request with automatic retry for rate limiting */ async delete<T>(url: string, params?: Record<string, string | number | boolean>): Promise<T> { return this.executeWithRetry(async () => { const response = await this.client.delete<T>(url, { params: this.addTeamId(params) as Record<string, string | number | boolean> }); return response.data; }); } /** * POST multipart/form-data request with automatic retry for rate limiting */ async postFormData<T>(url: string, formData: { getHeaders?: () => Record<string, string> }, params?: Record<string, string | number | boolean>): Promise<T> { return this.executeWithRetry(async () => { // form-data sets its own Content-Type with boundary, so we use its headers const headers = formData.getHeaders ? formData.getHeaders() : { 'Content-Type': 'multipart/form-data', }; const response = await this.client.post<T>(url, formData, { params: this.addTeamId(params), headers: { ...headers, 'Authorization': this.client.defaults.headers['Authorization'], }, maxContentLength: Infinity, maxBodyLength: Infinity, }); return response.data; }); } } /** * Create a Liara API client from environment variables */ export function createLiaraClient(): LiaraClient { const apiToken = process.env.LIARA_API_TOKEN; if (!apiToken) { throw new Error('LIARA_API_TOKEN environment variable is required'); } return new LiaraClient({ apiToken, teamId: process.env.LIARA_TEAM_ID, baseURL: process.env.LIARA_API_BASE_URL, }); }

Implementation Reference

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/razavioo/liara-mcp'

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