UNPKG

github-repos-manager-mcp

Version:

Model Context Protocol (MCP) server that enables your MCP client (e.g., Claude Desktop, Cline, Roo Code, Cursor, Windsurf, etc.) to interact with GitHub repositories using your GitHub personal access token.

370 lines (345 loc) 10.4 kB
// src/handlers/analytics.cjs const { getRepoOwnerAndName } = require("../utils/shared-utils.cjs"); const { formatTrafficData, formatLanguagesData, } = require("../utils/formatters.cjs"); const CacheService = require("../services/cache-service.cjs"); // Helper function to handle API calls with caching for data that doesn't need special formatting before returning async function fetchAndCacheGeneric(apiCallBound, cacheKey, ttl, ...apiParams) { let data = CacheService.getFromCache(cacheKey); if (data) { return { success: true, data, source: "cache" }; } data = await apiCallBound(...apiParams); CacheService.setToCache(cacheKey, data, ttl); return { success: true, data, source: "api" }; } module.exports = { get_repo_stats: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const statType = args.type || "traffic"; const cacheTTL = 1600; // Cache for 10 minutes try { let result; let cacheKey; const per = args.per || "day"; switch (statType) { case "clones": cacheKey = CacheService.generateRepoCacheKey( owner, repo, `stats_clones_${per}` ); result = await fetchAndCacheGeneric( apiService.getRepositoryTrafficClones.bind(apiService), cacheKey, cacheTTL, owner, repo, per ); break; case "views": cacheKey = CacheService.generateRepoCacheKey( owner, repo, `stats_views_${per}` ); result = await fetchAndCacheGeneric( apiService.getRepositoryTrafficViews.bind(apiService), cacheKey, cacheTTL, owner, repo, per ); break; case "popular_content": // maps to popular paths cacheKey = CacheService.generateRepoCacheKey( owner, repo, "stats_popular_paths" ); result = await fetchAndCacheGeneric( apiService.getRepositoryPopularPaths.bind(apiService), cacheKey, cacheTTL, owner, repo ); break; case "traffic": default: cacheKey = CacheService.generateRepoCacheKey( owner, repo, `stats_traffic_default_views_${per}` ); result = await fetchAndCacheGeneric( apiService.getRepositoryTrafficViews.bind(apiService), cacheKey, cacheTTL, owner, repo, per ); result.message = result.message ? result.message + " Displaying 'views' data for 'traffic' stat type." : "Displaying 'views' data for 'traffic' stat type."; break; } return result; } catch (error) { return { success: false, message: `Error in get_repo_stats (${statType}): ${error.message}`, error: error.toString(), }; } }, list_repo_topics: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const cacheKey = CacheService.generateRepoCacheKey(owner, repo, "topics"); const cacheTTL = 3600; // 1 hour try { const result = await fetchAndCacheGeneric( apiService.listRepositoryTopics.bind(apiService), cacheKey, cacheTTL, owner, repo ); return result; } catch (error) { return { success: false, message: `Error listing repository topics: ${error.message}`, error: error.toString(), }; } }, update_repo_topics: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const { names } = args; if (!Array.isArray(names)) { return { success: false, message: "Input 'names' must be an array of topics.", }; } try { const data = await apiService.updateRepositoryTopics(owner, repo, names); const cacheKey = CacheService.generateRepoCacheKey(owner, repo, "topics"); CacheService.deleteFromCache(cacheKey); return { success: true, data, message: "Repository topics updated successfully.", }; } catch (error) { return { success: false, message: `Error updating repository topics: ${error.message}`, error: error.toString(), }; } }, get_repo_languages: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const cacheKey = CacheService.generateRepoCacheKey( owner, repo, "languages" ); const cacheTTL = 3600 * 6; // 6 hours try { let rawData = CacheService.getFromCache(cacheKey); if (rawData) { return { success: true, data: formatLanguagesData(rawData), source: "cache", }; } rawData = await apiService.getRepositoryLanguages(owner, repo); CacheService.setToCache(cacheKey, rawData, cacheTTL); return { success: true, data: formatLanguagesData(rawData), source: "api", }; } catch (error) { return { success: false, message: `Error getting repository languages: ${error.message}`, error: error.toString(), }; } }, list_stargazers: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const per_page = args.per_page || 30; const page = args.page || 1; const cacheKey = CacheService.generateRepoCacheKey( owner, repo, `stargazers_p${page}_pp${per_page}` ); const cacheTTL = 3600; // 1 hour try { const result = await fetchAndCacheGeneric( apiService.listStargazers.bind(apiService), cacheKey, cacheTTL, owner, repo, per_page, page ); return result; } catch (error) { return { success: false, message: `Error listing stargazers: ${error.message}`, error: error.toString(), }; } }, list_watchers: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const per_page = args.per_page || 30; const page = args.page || 1; const cacheKey = CacheService.generateRepoCacheKey( owner, repo, `watchers_p${page}_pp${per_page}` ); const cacheTTL = 3600; // 1 hour try { const result = await fetchAndCacheGeneric( apiService.listWatchers.bind(apiService), cacheKey, cacheTTL, owner, repo, per_page, page ); return result; } catch (error) { return { success: false, message: `Error listing watchers: ${error.message}`, error: error.toString(), }; } }, list_forks: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const sort = args.sort || "newest"; const per_page = args.per_page || 30; const page = args.page || 1; const cacheKey = CacheService.generateRepoCacheKey( owner, repo, `forks_s${sort}_p${page}_pp${per_page}` ); const cacheTTL = 3600; // 1 hour try { const result = await fetchAndCacheGeneric( apiService.listForks.bind(apiService), cacheKey, cacheTTL, owner, repo, sort, per_page, page ); return result; } catch (error) { return { success: false, message: `Error listing forks: ${error.message}`, error: error.toString(), }; } }, get_repo_traffic: async (args, defaultRepoDetails, apiService) => { const { owner, repo } = getRepoOwnerAndName(args, defaultRepoDetails); const dataType = args.data_type || "views"; const cacheTTL = 3600; // 1 hour let cacheKeySuffix; const per = args.per || "day"; try { let apiMethodBound; let apiParams = [owner, repo]; switch (dataType) { case "views": cacheKeySuffix = `traffic_views_${per}`; apiMethodBound = apiService.getRepositoryTrafficViews.bind(apiService); apiParams.push(per); break; case "clones": cacheKeySuffix = `traffic_clones_${per}`; apiMethodBound = apiService.getRepositoryTrafficClones.bind(apiService); apiParams.push(per); break; case "popular_referrers": cacheKeySuffix = "traffic_popular_referrers"; apiMethodBound = apiService.getRepositoryPopularReferrers.bind(apiService); break; case "popular_paths": cacheKeySuffix = "traffic_popular_paths"; apiMethodBound = apiService.getRepositoryPopularPaths.bind(apiService); break; default: return { success: false, message: `Invalid data_type for get_repo_traffic: ${dataType}`, }; } const cacheKey = CacheService.generateRepoCacheKey( owner, repo, cacheKeySuffix ); if (dataType === "views") { let rawData = CacheService.getFromCache(cacheKey); if (rawData) { return { success: true, data: formatTrafficData(rawData), source: "cache", }; } rawData = await apiMethodBound(...apiParams); CacheService.setToCache(cacheKey, rawData, cacheTTL); return { success: true, data: formatTrafficData(rawData), source: "api", }; } else { return await fetchAndCacheGeneric( apiMethodBound, cacheKey, cacheTTL, ...apiParams ); } } catch (error) { return { success: false, message: `Error in get_repo_traffic (${dataType}): ${error.message}`, error: error.toString(), }; } }, };