Skip to main content
Glama
kurdin

GitHub Repos Manager MCP Server

advanced-features.cjs28.5 kB
/** * @fileoverview Handlers for advanced GitHub features. * This file contains real implementations for advanced GitHub functionality. */ const { validateRequired, getOwnerRepo } = require("../utils/shared-utils.cjs"); /** * Handler for code quality checks using GitHub's built-in code scanning. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Code scanning results or setup instructions. */ async function handleCodeQualityChecks(args, defaultRepo, apiService) { const { owner, repo } = getOwnerRepo(args, defaultRepo); const { tool_name = "codeql" } = args; // Check for existing code scanning alerts const alerts = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/code-scanning/alerts` ); // Get code scanning analyses let analyses = []; try { analyses = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/code-scanning/analyses` ); } catch (error) { // Code scanning might not be enabled if (error.message.includes('404')) { return { success: true, message: "Code scanning is not enabled for this repository.", data: { alerts: [], analyses: [], recommendations: [ "Enable GitHub Advanced Security to use code scanning", "Set up CodeQL analysis workflow in .github/workflows/", "Configure third-party security tools like Snyk or SonarCloud" ], setup_url: `https://github.com/${owner}/${repo}/security/code-scanning` } }; } throw error; } // Analyze the results const highSeverityAlerts = alerts.filter(alert => alert.rule?.severity === 'error' || alert.rule?.security_severity_level === 'high' ); const mediumSeverityAlerts = alerts.filter(alert => alert.rule?.severity === 'warning' || alert.rule?.security_severity_level === 'medium' ); return { success: true, message: `Code quality analysis completed for ${owner}/${repo}`, data: { repository: `${owner}/${repo}`, total_alerts: alerts.length, high_severity: highSeverityAlerts.length, medium_severity: mediumSeverityAlerts.length, low_severity: alerts.length - highSeverityAlerts.length - mediumSeverityAlerts.length, recent_analyses: analyses.slice(0, 5).map(analysis => ({ id: analysis.id, tool: analysis.tool?.name || 'Unknown', created_at: analysis.created_at, commit_sha: analysis.commit_sha?.substring(0, 7), results_count: analysis.results_count || 0 })), alerts_url: `https://github.com/${owner}/${repo}/security/code-scanning`, recommendations: alerts.length > 0 ? ["Review and fix high-severity alerts", "Set up automated security scanning"] : ["Code scanning is active with no current alerts", "Consider adding more security tools"] } }; } /** * Handler for custom dashboard data aggregation from GitHub API. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Custom dashboard data with repository metrics. */ async function handleCustomDashboards(args, defaultRepo, apiService) { const { action = "view", dashboard_id = "default" } = args; if (action === "view") { // Determine scope - repo-specific or user-wide let dashboardData = {}; if (args.owner && args.repo) { // Repository-specific dashboard const { owner, repo } = getOwnerRepo(args, defaultRepo); // Fetch repository metrics const [repoInfo, issues, prs, commits, contributors, languages] = await Promise.all([ apiService.makeGitHubRequest(`/repos/${owner}/${repo}`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/issues?state=all&per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/pulls?state=all&per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/commits?per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/contributors?per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/languages`) ]); // Process metrics const openIssues = issues.filter(issue => issue.state === 'open' && !issue.pull_request); const closedIssues = issues.filter(issue => issue.state === 'closed' && !issue.pull_request); const openPRs = prs.filter(pr => pr.state === 'open'); const mergedPRs = prs.filter(pr => pr.merged_at); // Recent activity (last 30 days) const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const recentCommits = commits.filter(commit => new Date(commit.commit.author.date) > thirtyDaysAgo ); dashboardData = { repository: `${owner}/${repo}`, overview: { stars: repoInfo.stargazers_count, forks: repoInfo.forks_count, watchers: repoInfo.subscribers_count, size_kb: repoInfo.size, default_branch: repoInfo.default_branch, visibility: repoInfo.private ? 'private' : 'public' }, issues: { total: issues.length, open: openIssues.length, closed: closedIssues.length, open_ratio: issues.length > 0 ? (openIssues.length / issues.length * 100).toFixed(1) : 0 }, pull_requests: { total: prs.length, open: openPRs.length, merged: mergedPRs.length, merge_ratio: prs.length > 0 ? (mergedPRs.length / prs.length * 100).toFixed(1) : 0 }, activity: { total_commits: commits.length, commits_last_30_days: recentCommits.length, contributors: contributors.length, top_contributor: contributors[0]?.login || 'N/A', last_update: repoInfo.updated_at }, languages: Object.keys(languages).map(lang => ({ name: lang, bytes: languages[lang], percentage: ((languages[lang] / Object.values(languages).reduce((a, b) => a + b, 0)) * 100).toFixed(1) })).slice(0, 5), health_score: calculateRepoHealthScore(repoInfo, openIssues, mergedPRs, recentCommits) }; } else { // User-wide dashboard const user = await apiService.makeGitHubRequest('/user'); const repos = await apiService.makeGitHubRequest('/user/repos?per_page=100&sort=updated'); const totalStars = repos.reduce((sum, repo) => sum + repo.stargazers_count, 0); const totalForks = repos.reduce((sum, repo) => sum + repo.forks_count, 0); const publicRepos = repos.filter(repo => !repo.private); const privateRepos = repos.filter(repo => repo.private); dashboardData = { user: user.login, profile: { name: user.name, public_repos: user.public_repos, followers: user.followers, following: user.following, created_at: user.created_at }, repositories: { total: repos.length, public: publicRepos.length, private: privateRepos.length, total_stars: totalStars, total_forks: totalForks, most_starred: repos.sort((a, b) => b.stargazers_count - a.stargazers_count)[0]?.name || 'N/A' }, recent_activity: repos.slice(0, 10).map(repo => ({ name: repo.name, updated: repo.updated_at, stars: repo.stargazers_count, language: repo.language })) }; } return { success: true, message: `Dashboard ${dashboard_id} generated successfully`, data: { dashboard_id, generated_at: new Date().toISOString(), ...dashboardData } }; } return { success: false, message: `Unsupported dashboard action: ${action}` }; } /** * Calculate repository health score based on various metrics */ function calculateRepoHealthScore(repo, openIssues, mergedPRs, recentCommits) { let score = 50; // Base score // Documentation if (repo.description) score += 5; if (repo.homepage) score += 5; // Activity const daysSinceUpdate = (Date.now() - new Date(repo.updated_at)) / (1000 * 60 * 60 * 24); if (daysSinceUpdate < 30) score += 15; else if (daysSinceUpdate < 90) score += 10; else if (daysSinceUpdate < 365) score += 5; // Issue management if (openIssues.length < 10) score += 10; else if (openIssues.length < 50) score += 5; // Recent activity if (recentCommits.length > 10) score += 10; else if (recentCommits.length > 5) score += 5; // Community if (repo.stargazers_count > 100) score += 5; if (repo.forks_count > 10) score += 5; return Math.min(100, Math.max(0, score)); } /** * Handler for automated repository reporting. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Generated report data. */ async function handleAutomatedReporting(args, defaultRepo, apiService) { const { owner, repo } = getOwnerRepo(args, defaultRepo); const { report_type = "activity", output_format = "json" } = args; let reportData = {}; const reportId = `${owner}-${repo}-${report_type}-${Date.now()}`; switch (report_type) { case "activity": // Generate activity report const [commits, issues, prs, contributors] = await Promise.all([ apiService.makeGitHubRequest(`/repos/${owner}/${repo}/commits?per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/issues?state=all&per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/pulls?state=all&per_page=100`), apiService.makeGitHubRequest(`/repos/${owner}/${repo}/contributors?per_page=100`) ]); // Last 30 days activity const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const recentCommits = commits.filter(commit => new Date(commit.commit.author.date) > thirtyDaysAgo ); const recentIssues = issues.filter(issue => new Date(issue.created_at) > thirtyDaysAgo ); const recentPRs = prs.filter(pr => new Date(pr.created_at) > thirtyDaysAgo ); reportData = { repository: `${owner}/${repo}`, period: "Last 30 days", commits: { total: commits.length, recent: recentCommits.length, top_authors: getTopCommitAuthors(recentCommits).slice(0, 5) }, issues: { total: issues.length, recent: recentIssues.length, open: issues.filter(i => i.state === 'open').length, closed: issues.filter(i => i.state === 'closed').length }, pull_requests: { total: prs.length, recent: recentPRs.length, merged: prs.filter(pr => pr.merged_at).length, open: prs.filter(pr => pr.state === 'open').length }, contributors: { total: contributors.length, top_contributors: contributors.slice(0, 10).map(c => ({ login: c.login, contributions: c.contributions })) } }; break; case "security": // Generate security report let vulnerabilityAlerts = []; let secretScanningAlerts = []; try { vulnerabilityAlerts = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/vulnerability-alerts` ); } catch (error) { // Vulnerability alerts might not be available } try { secretScanningAlerts = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/secret-scanning/alerts` ); } catch (error) { // Secret scanning might not be enabled } reportData = { repository: `${owner}/${repo}`, vulnerability_alerts: { total: vulnerabilityAlerts.length, alerts: vulnerabilityAlerts.slice(0, 10) }, secret_scanning: { total: secretScanningAlerts.length, alerts: secretScanningAlerts.slice(0, 10) }, security_recommendations: [ "Enable Dependabot alerts", "Enable secret scanning", "Use branch protection rules", "Require status checks", "Review and update dependencies regularly" ] }; break; case "issues": // Generate issues report const allIssues = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/issues?state=all&per_page=100` ); const openIssues = allIssues.filter(issue => issue.state === 'open' && !issue.pull_request); const closedIssues = allIssues.filter(issue => issue.state === 'closed' && !issue.pull_request); // Issue aging const oldIssues = openIssues.filter(issue => (Date.now() - new Date(issue.created_at)) > (90 * 24 * 60 * 60 * 1000) ); reportData = { repository: `${owner}/${repo}`, summary: { total_issues: openIssues.length + closedIssues.length, open: openIssues.length, closed: closedIssues.length, stale_issues: oldIssues.length }, labels: getIssueLabelStats(allIssues), aging: { new_7_days: openIssues.filter(i => (Date.now() - new Date(i.created_at)) < (7 * 24 * 60 * 60 * 1000) ).length, older_30_days: openIssues.filter(i => (Date.now() - new Date(i.created_at)) > (30 * 24 * 60 * 60 * 1000) ).length, older_90_days: oldIssues.length } }; break; default: throw new Error(`Unsupported report type: ${report_type}`); } return { success: true, message: `${report_type} report generated for ${owner}/${repo}`, data: { report_id: reportId, report_type, generated_at: new Date().toISOString(), output_format, ...reportData } }; } /** * Helper function to get top commit authors */ function getTopCommitAuthors(commits) { const authors = {}; commits.forEach(commit => { const author = commit.commit.author.name; authors[author] = (authors[author] || 0) + 1; }); return Object.entries(authors) .sort(([,a], [,b]) => b - a) .map(([name, count]) => ({ name, commits: count })); } /** * Helper function to get issue label statistics */ function getIssueLabelStats(issues) { const labels = {}; issues.forEach(issue => { issue.labels.forEach(label => { labels[label.name] = (labels[label.name] || 0) + 1; }); }); return Object.entries(labels) .sort(([,a], [,b]) => b - a) .slice(0, 10) .map(([name, count]) => ({ name, count })); } /** * Handler for GitHub notification management. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Notification management results. */ async function handleNotificationManagement(args, defaultRepo, apiService) { const { action = "list", thread_id, all = false, participating = false } = args; switch (action) { case "list": const params = new URLSearchParams(); if (all) params.append('all', 'true'); if (participating) params.append('participating', 'true'); const notifications = await apiService.makeGitHubRequest( `/notifications?${params.toString()}` ); return { success: true, message: `Found ${notifications.length} notifications`, data: { total: notifications.length, unread: notifications.filter(n => n.unread).length, notifications: notifications.slice(0, 20).map(notification => ({ id: notification.id, title: notification.subject.title, type: notification.subject.type, repository: notification.repository.full_name, unread: notification.unread, updated_at: notification.updated_at, url: notification.subject.url })) } }; case "mark_read": await apiService.makeGitHubRequest('/notifications', { method: 'PUT' }); return { success: true, message: "All notifications marked as read" }; case "get_thread": if (!thread_id) { throw new Error("thread_id is required for get_thread action"); } const thread = await apiService.makeGitHubRequest(`/notifications/threads/${thread_id}`); return { success: true, message: "Thread retrieved successfully", data: { id: thread.id, title: thread.subject.title, type: thread.subject.type, repository: thread.repository.full_name, unread: thread.unread, updated_at: thread.updated_at, subscription: thread.subscription } }; case "mark_thread_read": if (!thread_id) { throw new Error("thread_id is required for mark_thread_read action"); } await apiService.makeGitHubRequest(`/notifications/threads/${thread_id}`, { method: 'PATCH' }); return { success: true, message: `Thread ${thread_id} marked as read` }; default: throw new Error(`Unsupported notification action: ${action}`); } } /** * Handler for GitHub release management. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Release management results. */ async function handleReleaseManagement(args, defaultRepo, apiService) { const { owner, repo } = getOwnerRepo(args, defaultRepo); const { action = "list", release_id, tag_name, name, body, draft = false, prerelease = false } = args; switch (action) { case "list": const releases = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/releases?per_page=20` ); return { success: true, message: `Found ${releases.length} releases for ${owner}/${repo}`, data: { repository: `${owner}/${repo}`, total: releases.length, releases: releases.map(release => ({ id: release.id, tag_name: release.tag_name, name: release.name, draft: release.draft, prerelease: release.prerelease, published_at: release.published_at, assets_count: release.assets.length, download_count: release.assets.reduce((sum, asset) => sum + asset.download_count, 0), url: release.html_url })) } }; case "get_latest": const latestRelease = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/releases/latest` ); return { success: true, message: `Latest release: ${latestRelease.tag_name}`, data: { id: latestRelease.id, tag_name: latestRelease.tag_name, name: latestRelease.name, body: latestRelease.body, draft: latestRelease.draft, prerelease: latestRelease.prerelease, published_at: latestRelease.published_at, assets: latestRelease.assets.map(asset => ({ name: asset.name, size: asset.size, download_count: asset.download_count, download_url: asset.browser_download_url })), url: latestRelease.html_url } }; case "get": if (!release_id) { throw new Error("release_id is required for get action"); } const release = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/releases/${release_id}` ); return { success: true, message: `Release ${release.tag_name} retrieved`, data: { id: release.id, tag_name: release.tag_name, name: release.name, body: release.body, draft: release.draft, prerelease: release.prerelease, published_at: release.published_at, assets: release.assets.map(asset => ({ name: asset.name, size: asset.size, download_count: asset.download_count })) } }; case "create": if (!tag_name) { throw new Error("tag_name is required for create action"); } const createData = { tag_name, name: name || tag_name, body: body || '', draft, prerelease }; if (args.target_commitish) { createData.target_commitish = args.target_commitish; } const newRelease = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/releases`, { method: 'POST', body: JSON.stringify(createData), headers: { 'Content-Type': 'application/json' } } ); return { success: true, message: `Release ${tag_name} created successfully`, data: { id: newRelease.id, tag_name: newRelease.tag_name, name: newRelease.name, draft: newRelease.draft, prerelease: newRelease.prerelease, url: newRelease.html_url } }; case "delete": if (!release_id) { throw new Error("release_id is required for delete action"); } await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/releases/${release_id}`, { method: 'DELETE' } ); return { success: true, message: `Release ${release_id} deleted successfully` }; default: throw new Error(`Unsupported release action: ${action}`); } } /** * Handler for dependency analysis using GitHub's dependency APIs. * @param {object} args - The arguments for the tool. * @param {object} defaultRepo - Default repository configuration. * @param {object} apiService - GitHub API service instance. * @returns {Promise<object>} Dependency analysis results. */ async function handleDependencyAnalysis(args, defaultRepo, apiService) { const { owner, repo } = getOwnerRepo(args, defaultRepo); const { report_type = "vulnerabilities" } = args; let analysisData = {}; switch (report_type) { case "vulnerabilities": // Get Dependabot alerts let vulnerabilityAlerts = []; try { vulnerabilityAlerts = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/dependabot/alerts` ); } catch (error) { if (error.message.includes('404')) { return { success: true, message: "Dependabot alerts are not enabled for this repository", data: { repository: `${owner}/${repo}`, dependabot_enabled: false, vulnerabilities: [], recommendations: [ "Enable Dependabot alerts in repository settings", "Enable GitHub Advanced Security", "Review dependency files (package.json, requirements.txt, etc.)" ] } }; } throw error; } // Categorize by severity const critical = vulnerabilityAlerts.filter(alert => alert.security_vulnerability?.severity === 'critical'); const high = vulnerabilityAlerts.filter(alert => alert.security_vulnerability?.severity === 'high'); const medium = vulnerabilityAlerts.filter(alert => alert.security_vulnerability?.severity === 'medium'); const low = vulnerabilityAlerts.filter(alert => alert.security_vulnerability?.severity === 'low'); analysisData = { repository: `${owner}/${repo}`, dependabot_enabled: true, total_alerts: vulnerabilityAlerts.length, severity_breakdown: { critical: critical.length, high: high.length, medium: medium.length, low: low.length }, vulnerabilities: vulnerabilityAlerts.slice(0, 20).map(alert => ({ id: alert.number, state: alert.state, package: alert.dependency?.package?.name, manifest_path: alert.dependency?.manifest_path, severity: alert.security_vulnerability?.severity, summary: alert.security_vulnerability?.summary, created_at: alert.created_at, fixed_at: alert.fixed_at, url: alert.html_url })), recommendations: vulnerabilityAlerts.length > 0 ? [ "Review and fix critical and high severity vulnerabilities", "Update dependencies to latest secure versions", "Enable automated dependency updates" ] : [ "No vulnerabilities detected", "Keep dependencies updated", "Monitor for new vulnerabilities" ] }; break; case "licenses": // For license analysis, we'd typically need to analyze dependency files // This is a simplified version that checks known dependency files let dependencyInfo = []; try { // Check for common dependency files const packageFiles = ['package.json', 'requirements.txt', 'Gemfile', 'pom.xml', 'go.mod']; for (const file of packageFiles) { try { const content = await apiService.makeGitHubRequest( `/repos/${owner}/${repo}/contents/${file}` ); dependencyInfo.push({ file: file, exists: true, ecosystem: getEcosystemFromFile(file) }); } catch (error) { // File doesn't exist, continue } } } catch (error) { // Continue with what we have } analysisData = { repository: `${owner}/${repo}`, dependency_files: dependencyInfo, license_analysis: { status: "manual_review_required", note: "Full license analysis requires specialized tools", recommendations: [ "Use tools like FOSSA or Snyk for comprehensive license analysis", "Review dependency licenses manually", "Ensure license compatibility with your project" ] } }; break; case "graph": // Get dependency graph (requires GraphQL API) analysisData = { repository: `${owner}/${repo}`, dependency_graph: { status: "graphql_required", note: "Dependency graph analysis requires GraphQL API", alternatives: [ "Use GitHub's Insights > Dependency graph page", "Enable Dependabot for security analysis", "Use package manager specific tools (npm audit, pip-audit, etc.)" ] } }; break; default: throw new Error(`Unsupported dependency analysis type: ${report_type}`); } return { success: true, message: `Dependency analysis (${report_type}) completed for ${owner}/${repo}`, data: { analysis_type: report_type, generated_at: new Date().toISOString(), ...analysisData } }; } /** * Helper function to determine ecosystem from dependency file */ function getEcosystemFromFile(filename) { const ecosystems = { 'package.json': 'npm', 'requirements.txt': 'pip', 'Gemfile': 'ruby', 'pom.xml': 'maven', 'go.mod': 'go', 'Cargo.toml': 'cargo', 'composer.json': 'composer' }; return ecosystems[filename] || 'unknown'; } module.exports = { handleCodeQualityChecks, handleCustomDashboards, handleAutomatedReporting, handleNotificationManagement, handleReleaseManagement, handleDependencyAnalysis, };

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/kurdin/github-repos-manager-mcp'

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