Skip to main content
Glama
api-client.ts6.56 kB
import { API_ENDPOINT } from '@/consts/index.js'; import { formatError } from '@/utils/error/format.js'; import { assert, formatUrl, internalCache } from '@/utils/index.js'; /** * Unified FeiShu API Client * * Uses only FeiShu endpoint implementation, simplifying configuration and implementation */ import axios from 'axios'; import { stringify } from 'qs'; import { createRequestInterceptor, createResponseErrorInterceptor, createResponseSuccessInterceptor, } from './interceptors.js'; import { TokenManager } from './token-manager.js'; import type { Cache, Logger } from '@/typings/index.js'; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import type { ApiClientConfig, ApiResponse, PaginationOptions, Payload, RequestOptions, } from './types.js'; import { ApiError } from './types.js'; /** * Unified FeiShu API Client */ export class ApiClient { protected domain = API_ENDPOINT; protected appId!: string; protected appSecret!: string; protected cache!: Cache; protected disableTokenCache?: boolean; protected tokenManager!: TokenManager; protected logger!: Logger; protected httpInstance!: AxiosInstance; /** * Create new API client */ constructor(config: ApiClientConfig) { this.appId = config.appId; this.appSecret = config.appSecret; this.logger = config.logger; this.cache = config.cache || (internalCache as unknown as Cache); this.disableTokenCache = config.disableTokenCache; this.initializeHttpInstance(); this.initializeTokenManager(); } /** * Initialize HTTP client instance */ protected initializeHttpInstance(): void { // Create axios instance this.httpInstance = axios.create({ baseURL: this.domain, headers: { 'Content-Type': 'application/json', 'User-Agent': 'feishu-mcp-server/0.0.1', }, // Configure global parameter serializer paramsSerializer: { serialize: (params) => stringify(params, { arrayFormat: 'repeat' }), }, }); // Add interceptors this.httpInstance.interceptors.request.use( createRequestInterceptor(this.logger), ); // Add response interceptors this.httpInstance.interceptors.response.use( createResponseSuccessInterceptor(), createResponseErrorInterceptor(this.logger), ); } /** * Initialize token manager */ protected initializeTokenManager(): void { this.tokenManager = new TokenManager({ appId: this.appId, appSecret: this.appSecret, cache: this.cache, logger: this.logger, httpInstance: this.httpInstance, }); } /** * Convert pagination parameters to request parameters */ protected convertPaginationParams( pagination?: PaginationOptions, params: Record<string, unknown> = {}, ): Record<string, unknown> { if (!pagination) return params; const result = { ...params }; if (pagination.pageSize !== undefined) result.page_size = pagination.pageSize; if (pagination.pageToken) result.page_token = pagination.pageToken; return result; } /** * Send GET request */ protected async get<T = unknown>( url: string, params?: Record<string, unknown>, options?: RequestOptions, ): Promise<ApiResponse<T>> { return this.request<T>( { url, method: 'GET', params, }, options, ); } /** * Send paginated GET request */ protected async getList<T = unknown>( url: string, pagination?: PaginationOptions, additionalParams?: Record<string, unknown>, options?: RequestOptions, ): Promise<ApiResponse<T>> { // Merge pagination parameters and additional parameters const params = this.convertPaginationParams(pagination, additionalParams); return this.request<T>( { url, method: 'GET', params, }, options, ); } /** * Send POST request */ protected async post<T = unknown>( url: string, data?: unknown, params?: Record<string, unknown>, options?: RequestOptions, ): Promise<ApiResponse<T>> { return this.request<T>( { url, method: 'POST', data, params, }, options, ); } /** * Add authorization information to request */ protected async addAuthorizationHeader( config: AxiosRequestConfig, options?: RequestOptions, ): Promise<AxiosRequestConfig> { // Create new request config object to avoid modifying the original const newConfig = { ...config }; const targetOptions: Required<RequestOptions> = { params: options?.params || {}, data: options?.data || {}, headers: options?.headers || {}, path: options?.path || {}, }; // Get token and add authorization header const tenantAccessToken = await this.tokenManager.getTenantAccessToken(); if (tenantAccessToken) { newConfig.headers = newConfig.headers || {}; newConfig.headers.Authorization = `Bearer ${tenantAccessToken}`; } else { this.logger.warn('Failed to get token'); } // Merge request options if (Object.keys(targetOptions.params).length > 0) { newConfig.params = { ...newConfig.params, ...targetOptions.params }; } if (Object.keys(targetOptions.data).length > 0) { newConfig.data = { ...newConfig.data, ...targetOptions.data }; } return newConfig; } /** * Send authenticated request to API */ protected async request<T = unknown>( config: AxiosRequestConfig, options?: RequestOptions, ): Promise<ApiResponse<T>> { try { // Add authorization header const authenticatedConfig = await this.addAuthorizationHeader( config, options, ); // Record request if (this.logger.trace) { this.logger.trace(`Sending request [${config.method}]: ${config.url}`); } // Use axios instance to send request const response = await this.httpInstance.request< unknown, AxiosResponse<ApiResponse<T>> >({ ...authenticatedConfig, }); // Return response data return response.data; } catch (error) { // If already ApiError, throw directly if (error instanceof ApiError) { throw error; } // Other error handling this.logger.error( `API request failed: ${config.method} ${config.url}`, formatError(error, { structured: true }), ); throw error; } } }

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/sdd330/feishu-mcp-server'

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