Skip to main content
Glama
client.ts5.51 kB
/** * GitHub GraphQL client with authentication and error handling * Following GitHub GraphQL API best practices: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql */ import { graphql } from '@octokit/graphql'; import { extractRateLimit, getWaitTime, sleep, shouldWaitForRateLimit } from '../utils/rateLimit.js'; export interface GraphQLResponse<T> { data: T; headers: Record<string, string>; } export class GitHubClient { private client: typeof graphql; private requestId: number = 0; constructor(token: string) { if (!token) { throw new Error('GITHUB_TOKEN environment variable is required'); } // Initialize GraphQL client with authentication // See: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql this.client = graphql.defaults({ headers: { authorization: `token ${token}`, }, }); } /** * Executes a GraphQL query with rate limit handling * * Rate limits: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit * * @param query - GraphQL query string * @param variables - Query variables * @returns GraphQL response with data and headers */ async query<T = any>( query: string, variables: Record<string, any> = {} ): Promise<GraphQLResponse<T>> { const currentRequestId = ++this.requestId; try { // Log request (without sensitive data) if (process.env.DEBUG === 'true') { console.log(`[Request ${currentRequestId}] Executing GraphQL query`, { variables: this.sanitizeVariables(variables), }); } const response = (await this.client(query, variables)) as any; // Extract rate limit info from response headers // GitHub includes rate limit info in response headers const rateLimit = extractRateLimit(response.headers || {}); if (rateLimit) { if (process.env.DEBUG === 'true') { console.log(`[Request ${currentRequestId}] Rate limit: ${rateLimit.remaining}/${rateLimit.limit}`); } // Wait if approaching rate limit to avoid hitting it if (shouldWaitForRateLimit(rateLimit)) { const waitTime = getWaitTime(rateLimit); if (waitTime > 0) { console.warn(`[Request ${currentRequestId}] Approaching rate limit, waiting ${waitTime}ms`); await sleep(waitTime); } } } // GitHub GraphQL response structure: { data: {...}, headers: {...} } // @octokit/graphql returns data directly or wrapped const responseData = (response as any).data || response; const responseHeaders = response.headers || {}; return { data: responseData, headers: responseHeaders, }; } catch (error: any) { // Handle authentication errors // See: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql if (error.status === 401 || error.message?.includes('Bad credentials')) { throw new Error( 'GitHub authentication failed. Check GITHUB_TOKEN environment variable and ensure it is valid.' ); } // Handle access forbidden errors (common for private org repos) // See: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql if (error.status === 403 && !error.message?.includes('rate limit')) { const errorMsg = error.message || 'Access forbidden'; if (errorMsg.includes('Could not resolve') || errorMsg.includes('Repository')) { throw new Error( `Access denied to repository. For private organization repositories, ensure:\n` + ` 1. Token has 'repo' scope (for private repos)\n` + ` 2. Token has 'read:org' scope (for organization repos)\n` + ` 3. Your account is a member of the organization\n` + ` 4. Organization allows third-party access (if applicable)` ); } throw new Error(`GitHub API access forbidden: ${errorMsg}`); } // Handle rate limit errors // See: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit if (error.status === 403 && error.message?.includes('rate limit')) { const rateLimit = extractRateLimit(error.headers || {}); if (rateLimit) { const waitTime = getWaitTime(rateLimit); const resetTime = new Date(rateLimit.resetAt * 1000).toISOString(); throw new Error( `GitHub API rate limit exceeded. Reset at ${resetTime}. Wait ${waitTime}ms before retrying.` ); } throw new Error('GitHub API rate limit exceeded'); } // Log error with request ID console.error(`[Request ${currentRequestId}] GraphQL query failed:`, error.message); // Re-throw with more context if available if (error.errors) { const errorMessages = error.errors.map((e: any) => e.message).join('; '); throw new Error(`GraphQL errors: ${errorMessages}`); } throw error; } } /** * Sanitizes variables for logging (removes sensitive data) */ private sanitizeVariables(variables: Record<string, any>): Record<string, any> { const sanitized = { ...variables }; // Remove any potential sensitive fields delete sanitized.token; delete sanitized.password; return sanitized; } }

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/radireddy/github-mcp'

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