Skip to main content
Glama

mcp-github-project-manager

BaseRepository.ts6.22 kB
import { Octokit } from "@octokit/rest"; import { GitHubError, OctokitInstance } from "../types"; import { GitHubErrorHandler } from "../GitHubErrorHandler"; import { GitHubConfig } from "../GitHubConfig"; // Fixed import path import { Resource, ResourceStatus } from "../../../domain/resource-types"; import { GitHubApiUtil, PaginationOptions } from "../util/GitHubApiUtil"; import { ILogger, getLogger } from "../../logger"; export interface IGitHubRepository { readonly octokit: OctokitInstance; readonly config: GitHubConfig; } export abstract class BaseGitHubRepository implements IGitHubRepository { private readonly errorHandler: GitHubErrorHandler; protected readonly retryAttempts: number = 3; protected readonly apiUtil: GitHubApiUtil; protected readonly logger: ILogger; constructor( public readonly octokit: OctokitInstance, public readonly config: GitHubConfig ) { this.errorHandler = new GitHubErrorHandler(); this.apiUtil = GitHubApiUtil.getInstance(); this.logger = getLogger(this.constructor.name); } protected get owner(): string { return this.config.owner; } protected get repo(): string { return this.config.repo; } protected get token(): string { return this.config.token; } /** * Execute operation with automatic retries and rate limit handling */ protected async withRetry<T>( operation: () => Promise<T>, context?: string ): Promise<T> { let lastError: unknown; let error: unknown; for (let attempt = 0; attempt < this.retryAttempts; attempt++) { try { // Check if we should throttle due to rate limits if (await this.apiUtil.shouldThrottle(this.octokit)) { const delay = await this.apiUtil.calculateRequestDelay(this.octokit); await new Promise(resolve => setTimeout(resolve, delay)); } return await operation(); } catch (e) { error = e; lastError = e; const isRetryable = this.errorHandler.isRetryableError(error); const isLastAttempt = attempt === this.retryAttempts - 1; if (!isRetryable || isLastAttempt) { throw this.errorHandler.handleError( error, isLastAttempt ? `${context} (max retries exceeded)` : context ); } const headers = (error as GitHubError)?.response?.headers || {}; const delay = this.errorHandler.calculateRetryDelay(headers); await new Promise(resolve => setTimeout(resolve, delay)); } } throw this.errorHandler.handleError(lastError, context); } /** * Execute GraphQL query with rate limiting support */ protected async graphql<T>( query: string, variables: Record<string, unknown> = {} ): Promise<T> { return this.withRetry( () => this.octokit.graphql<T>(query, { ...variables, owner: this.owner, repo: this.repo, }), 'executing GraphQL query' ); } /** * Handle GraphQL errors consistently */ protected handleGraphQLError(error: unknown): Error { this.logger.error('GraphQL operation failed', error); return this.errorHandler.handleError(error, 'GraphQL operation'); } /** * Execute REST API call with rate limiting support */ protected async rest<T>( operation: (params: any) => Promise<{ data: T }>, params?: Record<string, unknown> ): Promise<T> { const result = await this.withRetry( () => operation(this.getRequestParams(params)), 'executing REST API call' ); return result.data; } /** * Execute paginated REST API call with comprehensive pagination support */ protected async paginatedRest<T>( operation: (params: any) => Promise<{ data: T[] }>, params?: Record<string, unknown>, paginationOptions?: PaginationOptions ): Promise<T[]> { const finalParams = this.getRequestParams(params); return this.apiUtil.paginateRequest<T>( (paginationParams) => operation({ ...finalParams, ...paginationParams }), paginationOptions ); } /** * Execute paginated GraphQL query with cursor-based pagination support */ protected async paginatedGraphQL<T>( query: string, getNodesAndPageInfo: (data: any) => { pageInfo: { hasNextPage: boolean; endCursor?: string }; nodes: T[]; }, variables: Record<string, unknown> = {}, options: { pageSize?: number; maxItems?: number; initialCursor?: string } = {} ): Promise<T[]> { return this.apiUtil.paginateGraphQL<T>( async ({ cursor, pageSize }) => { const data = await this.graphql(query, { ...variables, first: pageSize, after: cursor, owner: this.owner, repo: this.repo, }); return getNodesAndPageInfo(data); }, options ); } /** * Get rate limit information */ protected async getRateLimit() { return this.apiUtil.getRateLimit(this.octokit); } protected getRequestParams<T extends Record<string, unknown>>( params?: Partial<T> ): T & { owner: string; repo: string } { return { owner: this.owner, repo: this.repo, ...params, } as T & { owner: string; repo: string }; } protected toISOString(date: string | Date): string { if (date instanceof Date) { return date.toISOString(); } return new Date(date).toISOString(); } protected parseDate(date: string | null | undefined): string | undefined { if (!date) return undefined; return new Date(date).toISOString(); } protected convertGitHubStatus(githubStatus: "open" | "closed"): ResourceStatus { return githubStatus === "open" ? ResourceStatus.ACTIVE : ResourceStatus.CLOSED; } protected convertToGitHubStatus(status: ResourceStatus): "open" | "closed" { switch (status) { case ResourceStatus.ACTIVE: case ResourceStatus.PLANNED: return "open"; case ResourceStatus.CLOSED: case ResourceStatus.COMPLETED: case ResourceStatus.ARCHIVED: case ResourceStatus.DELETED: return "closed"; default: return "closed"; } } }

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/kunwarVivek/mcp-github-project-manager'

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