Skip to main content
Glama
naoto24kawa

Composer Package README MCP Server

by naoto24kawa
github-api.ts3.94 kB
import { logger } from '../utils/logger.js'; import { handleApiError, handleHttpError, withRetry } from '../utils/error-handler.js'; import { API_CONSTANTS } from '../utils/constants.js'; import { RepositoryInfo } from '../types/index.js'; export class GitHubApiClient { private readonly baseUrl = 'https://api.github.com'; private readonly token?: string | undefined; private readonly timeout: number; constructor(token?: string, timeout?: number) { this.token = token; this.timeout = timeout || API_CONSTANTS.DEFAULT_TIMEOUT_MS; if (!this.token) { logger.warn('GitHub token not provided. Rate limits will be lower.'); } } async getReadme(owner: string, repo: string): Promise<string> { const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/readme`; return withRetry(async () => { logger.debug(`Fetching README from GitHub: ${owner}/${repo}`); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const headers: Record<string, string> = { 'Accept': 'application/vnd.github.v3.raw', 'User-Agent': API_CONSTANTS.USER_AGENT, }; if (this.token) { headers['Authorization'] = `token ${this.token}`; } const response = await fetch(url, { signal: controller.signal, headers, }); if (!response.ok) { handleHttpError(response.status, response, `GitHub README for ${owner}/${repo}`); } const readmeContent = await response.text(); logger.debug(`Successfully fetched README from GitHub: ${owner}/${repo}`); return readmeContent; } catch (error) { if ((error as Error).name === 'AbortError') { handleApiError(new Error('Request timeout'), `GitHub README for ${owner}/${repo}`); } handleApiError(error, `GitHub README for ${owner}/${repo}`); } finally { clearTimeout(timeoutId); } }, API_CONSTANTS.MAX_RETRIES, API_CONSTANTS.BASE_RETRY_DELAY_MS, `GitHub API getReadme(${owner}/${repo})`); } parseRepositoryUrl(repositoryUrl: string): { owner: string; repo: string } | null { try { // Handle various GitHub URL formats const patterns = [ /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/, /^git\+https:\/\/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/, /^git:\/\/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/, /^git@github\.com:([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/, ]; for (const pattern of patterns) { const match = repositoryUrl.match(pattern); if (match && match[1] && match[2]) { return { owner: match[1], repo: match[2], }; } } logger.debug(`Could not parse GitHub repository URL: ${repositoryUrl}`); return null; } catch (error) { logger.debug(`Error parsing repository URL: ${repositoryUrl}`, { error }); return null; } } async getReadmeFromRepository(repository: RepositoryInfo): Promise<string | null> { if (repository.type !== 'git') { return null; } const parsed = this.parseRepositoryUrl(repository.url); if (!parsed) { return null; } try { return await this.getReadme(parsed.owner, parsed.repo); } catch (error) { logger.debug(`Failed to fetch README from GitHub: ${parsed.owner}/${parsed.repo}`, { error }); return null; } } isRateLimited(): boolean { // TODO: Implement rate limit tracking return false; } getRateLimitStatus(): { limit: number; remaining: number; resetTime: number; } | null { // TODO: Implement rate limit status tracking return null; } } export const githubApi = new GitHubApiClient();

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/naoto24kawa/composer-package-readme-mcp-server'

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