Skip to main content
Glama

MCP Advisor

MIT License
88
64
  • Apple
  • Linux
githubUtils.ts6.44 kB
import logger from './logger.js'; /** * Interface for GitHub repository information */ interface GitHubRepoInfo { owner: string; repo: string; branch?: string; } /** * Parse a GitHub URL to extract owner, repo, and branch information * @param url GitHub repository URL * @returns Parsed repository information or null if invalid URL */ export function parseGitHubUrl(url: string): GitHubRepoInfo | null { try { // Handle different GitHub URL formats const githubRegex = /github\.com\/([^\/]+)\/([^\/]+)(?:\/tree\/([^\/]+))?/; const match = url.match(githubRegex); if (!match) return null; const [, owner, repo, branch = 'main'] = match; // Remove .git suffix if present const cleanRepo = repo.endsWith('.git') ? repo.slice(0, -4) : repo; return { owner, repo: cleanRepo, branch, }; } catch (error) { logger.error?.(`Failed to parse GitHub URL: ${url}`, { error }); return null; } } /** * Fetch README.md content from GitHub using the API * @param repoInfo Repository information * @returns README content as markdown string or null if not found */ async function fetchReadmeUsingGitHubApi( repoInfo: GitHubRepoInfo, ): Promise<string | null> { try { const apiUrl = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/readme`; // Create an AbortController to implement timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout try { const response = await fetch(apiUrl, { headers: { Accept: 'application/vnd.github.raw+json', 'User-Agent': 'mcpadvisor', }, signal: controller.signal, }); if (!response.ok) { throw new Error( `GitHub API returned ${response.status}: ${response.statusText}`, ); } return await response.text(); } finally { clearTimeout(timeoutId); } } catch (error) { // Don't log timeout errors as warnings if (error instanceof Error && error.name === 'AbortError') { logger.info?.( `GitHub API request timed out for ${repoInfo.owner}/${repoInfo.repo}`, ); } else { logger.warn?.( `Failed to fetch README using GitHub API: ${error instanceof Error ? error.message : String(error)}`, ); } return null; } } /** * Fetch README.md content from GitHub using jsDelivr CDN * @param repoInfo Repository information * @returns README content as markdown string or null if not found */ async function fetchReadmeUsingJsDelivr( repoInfo: GitHubRepoInfo, ): Promise<string | null> { try { // jsDelivr URL format: https://cdn.jsdelivr.net/gh/user/repo@branch/file const cdnUrl = `https://cdn.jsdelivr.net/gh/${repoInfo.owner}/${repoInfo.repo}@${repoInfo.branch}/README.md`; // Create an AbortController to implement timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout try { const response = await fetch(cdnUrl, { signal: controller.signal, }); if (!response.ok) { throw new Error( `jsDelivr CDN returned ${response.status}: ${response.statusText}`, ); } return await response.text(); } finally { clearTimeout(timeoutId); } } catch (error) { // Don't log timeout errors as warnings if (error instanceof Error && error.name === 'AbortError') { logger.info?.( `jsDelivr request timed out for ${repoInfo.owner}/${repoInfo.repo}`, ); } else { logger.warn?.( `Failed to fetch README using jsDelivr: ${error instanceof Error ? error.message : String(error)}`, ); } return null; } } /** * Fetch README.md content from GitHub using raw.githubusercontent.com * @param repoInfo Repository information * @returns README content as markdown string or null if not found */ async function fetchReadmeUsingRawGitHub( repoInfo: GitHubRepoInfo, ): Promise<string | null> { try { const rawUrl = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/${repoInfo.branch}/README.md`; // Create an AbortController to implement timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout try { const response = await fetch(rawUrl, { signal: controller.signal, }); if (!response.ok) { throw new Error( `Raw GitHub returned ${response.status}: ${response.statusText}`, ); } return await response.text(); } finally { clearTimeout(timeoutId); } } catch (error) { // Don't log timeout errors as warnings if (error instanceof Error && error.name === 'AbortError') { logger.info?.( `Raw GitHub request timed out for ${repoInfo.owner}/${repoInfo.repo}`, ); } else { logger.warn?.( `Failed to fetch README using raw GitHub: ${error instanceof Error ? error.message : String(error)}`, ); } return null; } } /** * Fetch README.md content from a GitHub repository URL * Tries multiple methods in sequence for better reliability * @param url GitHub repository URL * @returns README content as markdown string or null if not found */ export async function fetchGitHubReadme(url: string): Promise<string | null> { const repoInfo = parseGitHubUrl(url); if (!repoInfo) { logger.error?.(`Invalid GitHub URL: ${url}`); return null; } logger.info?.(`Fetching README for ${repoInfo.owner}/${repoInfo.repo}`); // Try multiple methods in sequence const methods = [ { name: 'GitHub API', fn: fetchReadmeUsingGitHubApi }, { name: 'jsDelivr CDN', fn: fetchReadmeUsingJsDelivr }, { name: 'Raw GitHub', fn: fetchReadmeUsingRawGitHub }, ]; for (const { name, fn } of methods) { try { const content = await fn(repoInfo); if (content) { logger.info?.(`Successfully fetched README using ${name}`); return content; } } catch (error) { logger.warn?.( `Method ${name} failed: ${error instanceof Error ? error.message : String(error)}`, ); } } logger.error?.( `Failed to fetch README for ${repoInfo.owner}/${repoInfo.repo} using all available methods`, ); return null; }

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/istarwyh/mcpadvisor'

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