Skip to main content
Glama

Open Search MCP

by flyanima
MIT License
2
  • Apple
  • Linux
api-manager.ts6.42 kB
/** * 统一API管理框架 * 提供重试机制、缓存、错误处理和监控功能 */ import { Logger } from './logger.js'; interface APICallOptions { retries?: number; retryDelay?: number; timeout?: number; cache?: boolean; cacheTTL?: number; } interface APIResponse<T = any> { success: boolean; data?: T; error?: string; cached?: boolean; retryCount?: number; responseTime?: number; } interface CacheEntry { data: any; timestamp: number; ttl: number; } /** * API管理器类 */ export class APIManager { private logger: Logger; private cache: Map<string, CacheEntry> = new Map(); private rateLimits: Map<string, number> = new Map(); private errorCounts: Map<string, number> = new Map(); constructor() { this.logger = new Logger('APIManager'); // 定期清理过期缓存 setInterval(() => this.cleanExpiredCache(), 60000); // 每分钟清理一次 } /** * 执行API调用,带重试和缓存机制 */ async callAPI<T>( apiFunction: () => Promise<T>, cacheKey: string, options: APICallOptions = {} ): Promise<APIResponse<T>> { const startTime = Date.now(); const { retries = 3, retryDelay = 1000, timeout = 10000, cache = true, cacheTTL = 300000 // 5分钟默认缓存 } = options; // 检查缓存 if (cache) { const cachedResult = this.getFromCache(cacheKey); if (cachedResult) { this.logger.debug(`Cache hit for ${cacheKey}`); return { success: true, data: cachedResult, cached: true, responseTime: Date.now() - startTime }; } } // 检查速率限制 if (this.isRateLimited(cacheKey)) { return { success: false, error: 'Rate limit exceeded', responseTime: Date.now() - startTime }; } let lastError: Error | null = null; let retryCount = 0; // 重试循环 for (let attempt = 0; attempt <= retries; attempt++) { try { // 设置超时 const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => reject(new Error('Request timeout')), timeout); }); const result = await Promise.race([ apiFunction(), timeoutPromise ]); // 成功,缓存结果 if (cache) { this.setCache(cacheKey, result, cacheTTL); } // 重置错误计数 this.errorCounts.delete(cacheKey); return { success: true, data: result, cached: false, retryCount: attempt, responseTime: Date.now() - startTime }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); retryCount = attempt; this.logger.warn(`API call failed (attempt ${attempt + 1}/${retries + 1}): ${lastError.message}`); // 记录错误 this.incrementErrorCount(cacheKey); // 如果不是最后一次尝试,等待后重试 if (attempt < retries) { await this.delay(retryDelay * Math.pow(2, attempt)); // 指数退避 } } } // 所有重试都失败了 return { success: false, error: lastError?.message || 'Unknown error', retryCount, responseTime: Date.now() - startTime }; } /** * 批量API调用 */ async callMultipleAPIs<T>( apiCalls: Array<{ function: () => Promise<T>; cacheKey: string; options?: APICallOptions; }>, concurrent: boolean = true ): Promise<APIResponse<T>[]> { if (concurrent) { // 并发执行 return await Promise.all( apiCalls.map(call => this.callAPI(call.function, call.cacheKey, call.options) ) ); } else { // 顺序执行 const results: APIResponse<T>[] = []; for (const call of apiCalls) { const result = await this.callAPI(call.function, call.cacheKey, call.options); results.push(result); } return results; } } /** * 获取API健康状态 */ getAPIHealth(apiKey: string): { errorRate: number; lastError: number; isHealthy: boolean; } { const errorCount = this.errorCounts.get(apiKey) || 0; const lastError = this.rateLimits.get(apiKey) || 0; const errorRate = errorCount / 100; // 假设基于最近100次调用 return { errorRate, lastError, isHealthy: errorRate < 0.1 && (Date.now() - lastError) > 60000 // 错误率<10%且最后错误>1分钟前 }; } /** * 清理所有缓存 */ clearCache(): void { this.cache.clear(); this.logger.info('All cache cleared'); } /** * 获取缓存统计 */ getCacheStats(): { size: number; hitRate: number; entries: string[]; } { return { size: this.cache.size, hitRate: 0, // 需要实现命中率统计 entries: Array.from(this.cache.keys()) }; } // 私有方法 private getFromCache(key: string): any | null { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); return null; } return entry.data; } private setCache(key: string, data: any, ttl: number): void { this.cache.set(key, { data, timestamp: Date.now(), ttl }); } private cleanExpiredCache(): void { const now = Date.now(); let cleaned = 0; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); cleaned++; } } if (cleaned > 0) { this.logger.debug(`Cleaned ${cleaned} expired cache entries`); } } private isRateLimited(key: string): boolean { const lastCall = this.rateLimits.get(key) || 0; const minInterval = 1000; // 最小间隔1秒 if (Date.now() - lastCall < minInterval) { return true; } this.rateLimits.set(key, Date.now()); return false; } private incrementErrorCount(key: string): void { const current = this.errorCounts.get(key) || 0; this.errorCounts.set(key, current + 1); } private delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } } // 全局API管理器实例 export const apiManager = new APIManager();

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/flyanima/open-search-mcp'

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