Skip to main content
Glama

context-awesome

by bh-rat
api-client.ts7.16 kB
import fetch from 'node-fetch'; import { FindSectionParams, FindSectionResponse, GetItemsParams, GetItemsResponse, APIError, } from './types.js'; export class AwesomeContextAPIClient { private baseUrl: string; private apiKey?: string; private debug: boolean; constructor(baseUrl: string, apiKey?: string, debug = false) { this.baseUrl = baseUrl.replace(/\/$/, ''); this.apiKey = apiKey; this.debug = debug; } private log(...args: any[]) { if (this.debug) { console.error('[API Client]', ...args); } } private async request<T>( endpoint: string, params?: Record<string, any> ): Promise<T> { const url = new URL(`${this.baseUrl}${endpoint}`); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { url.searchParams.append(key, String(value)); } }); } const headers: Record<string, string> = { 'Content-Type': 'application/json', 'X-Source': 'context-awesome', }; if (this.apiKey) { headers['Authorization'] = `Bearer ${this.apiKey}`; } this.log(`Request: ${url.toString()}`); // Add timeout to prevent hanging requests const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout try { const response = await fetch(url.toString(), { method: 'GET', headers, signal: controller.signal, }); clearTimeout(timeoutId); const data = await response.json() as any; if (!response.ok) { let errorMessage = data.message || `HTTP ${response.status}: ${response.statusText}`; let errorCode = data.error || 'API_ERROR'; // Provide better error messages for specific status codes if (response.status === 429) { errorMessage = 'Rate limited due to too many requests. Please try again later.'; errorCode = 'RATE_LIMIT'; } else if (response.status === 401) { errorMessage = 'Unauthorized. Please check your API key.'; errorCode = 'UNAUTHORIZED'; } else if (response.status === 404) { errorMessage = 'The requested resource was not found.'; errorCode = 'NOT_FOUND'; } const error: APIError = { code: errorCode, message: errorMessage, statusCode: response.status, }; throw error; } this.log(`Response:`, data); return data as T; } catch (error: any) { clearTimeout(timeoutId); if (error.code) { throw error; } // Handle timeout specifically if (error.name === 'AbortError') { const apiError: APIError = { code: 'TIMEOUT', message: 'Request timeout after 30 seconds', }; throw apiError; } const apiError: APIError = { code: 'NETWORK_ERROR', message: `Failed to connect to API: ${error.message}`, }; throw apiError; } } async findSections(params: FindSectionParams): Promise<FindSectionResponse> { this.log('Finding sections with params:', params); const response = await this.request<any>('/api/find-section', { query: params.query, // Changed from 'q' to 'query' confidence: params.confidence, limit: params.limit, }); // Handle both 'results' and 'sections' response formats const sections = response.results || response.sections || []; return { sections: sections.map((section: any) => ({ id: section.id || section._id || '', listId: section.listId || '', listName: section.listName || section.list_name || '', githubRepo: section.githubRepo || section.github_repo || '', category: section.category || section.section || '', subcategory: section.subcategory || section.sub_category || '', itemCount: section.itemCount || section.item_count || 0, confidence: section.confidence || section.score || 0, description: section.description || '', })), total: response.total || sections.length, }; } async getItems(params: GetItemsParams): Promise<GetItemsResponse> { this.log('Getting items with params:', params); if (!params.listId && !params.githubRepo) { const error: APIError = { code: 'INVALID_PARAMS', message: 'Either listId or githubRepo must be provided', }; throw error; } const response = await this.request<any>('/api/get-items', { listId: params.listId, githubRepo: params.githubRepo, section: params.section, subcategory: params.subcategory, limit: params.tokens ? Math.floor(params.tokens / 50) : undefined, offset: params.offset, }); const items = response.items || response.data || []; const metadata = response.metadata || response.meta || {}; const tokenCount = this.estimateTokens(items); const tokenLimit = params.tokens || 10000; const truncated = tokenCount > tokenLimit; const truncatedItems = truncated ? this.truncateToTokenLimit(items, tokenLimit) : items; return { items: truncatedItems.map((item: any) => ({ id: item.id || item._id || '', name: item.name || item.title || '', description: item.description || '', url: item.url || item.link || '', githubStars: item.stars || item.githubStars || item.github_stars, githubRepo: item.repo || item.githubRepo || item.github_repo, tags: item.tags || [], lastUpdated: item.lastUpdated || item.updated_at || item.last_updated, })), metadata: { list: { id: metadata.listId || metadata.list_id || params.listId || '', name: metadata.listName || metadata.list_name || '', githubRepo: metadata.githubRepo || metadata.github_repo || params.githubRepo || '', description: metadata.description || '', totalItems: metadata.totalItems || metadata.total_items || items.length, }, section: metadata.section || params.section, subcategory: metadata.subcategory || params.subcategory, totalItems: metadata.totalItems || metadata.total_items || items.length, offset: metadata.offset || params.offset || 0, hasMore: metadata.hasMore || metadata.has_more || false, }, tokenUsage: { used: this.estimateTokens(truncatedItems), limit: tokenLimit, truncated, }, }; } private estimateTokens(items: any[]): number { const text = JSON.stringify(items); return Math.ceil(text.length / 4); } private truncateToTokenLimit(items: any[], limit: number): any[] { const result: any[] = []; let currentTokens = 0; for (const item of items) { const itemTokens = this.estimateTokens([item]); if (currentTokens + itemTokens > limit) { break; } result.push(item); currentTokens += itemTokens; } return result; } }

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/bh-rat/context-awesome'

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