Skip to main content
Glama
kurdin

GitHub Repos Manager MCP Server

index.cjs38.5 kB
#!/usr/bin/env node // Import all handler modules const GitHubAPIService = require("./services/github-api.cjs"); const repositoryHandlerFunctions = require("./handlers/repository.cjs"); const issueHandlerFunctions = require("./handlers/issues.cjs"); const commentHandlerFunctions = require("./handlers/comments.cjs"); const pullRequestHandlerFunctions = require("./handlers/pull-requests.cjs"); const userHandlerFunctions = require("./handlers/users.cjs"); const labelsHandlerFunctions = require("./handlers/labels.cjs"); const milestonesHandlerFunctions = require("./handlers/milestones.cjs"); const branchCommitHandlerFunctions = require("./handlers/branches-commits.cjs"); const EnhancedPullRequestHandlersModule = require("./handlers/enhanced-pull-requests.cjs"); const FileManagementHandlersModule = require("./handlers/file-management.cjs"); const SecurityHandlersModule = require("./handlers/security.cjs"); const AnalyticsHandlersModule = require("./handlers/analytics.cjs"); const SearchHandlersModule = require("./handlers/search.cjs"); const OrganizationHandlersModule = require("./handlers/organizations.cjs"); const ProjectsHandlersModule = require("./handlers/projects.cjs"); const WorkflowsHandlersModule = require("./handlers/workflows.cjs"); const AdvancedFeaturesHandlerModule = require("./handlers/advanced-features.cjs"); const toolsConfig = require("./utils/tools-config.cjs"); const { formatHandlerResponse } = require("./utils/response-formatter.cjs"); const { handleError } = require("./utils/error-handler.cjs"); class GitHubMCPServer { constructor(config = {}, sdkModules = {}) { this.Server = sdkModules.Server; this.StdioServerTransport = sdkModules.StdioServerTransport; this.CallToolRequestSchema = sdkModules.CallToolRequestSchema; this.ListToolsRequestSchema = sdkModules.ListToolsRequestSchema; this.defaultOwner = null; this.defaultRepo = null; this.disabledTools = new Set(); this.allowedTools = null; // null means all tools allowed, Set means only specific tools this.allowedRepos = null; // null means all repos allowed, Set means only specific repos/owners const token = process.env.GH_TOKEN ? process.env.GH_TOKEN.trim() : undefined; this.api = new GitHubAPIService(token); this.enhancedPrHandler = EnhancedPullRequestHandlersModule; this.fileManagementHandler = FileManagementHandlersModule; this.securityHandler = SecurityHandlersModule; this.analyticsHandler = AnalyticsHandlersModule; this.searchHandler = SearchHandlersModule; this.organizationHandler = OrganizationHandlersModule; this.projectsHandler = ProjectsHandlersModule; this.workflowsHandler = WorkflowsHandlersModule; this.advancedFeaturesHandler = AdvancedFeaturesHandlerModule; // Set default repository if provided this.defaultOwner = config.defaultOwner || process.env.GH_DEFAULT_OWNER; this.defaultRepo = config.defaultRepo || process.env.GH_DEFAULT_REPO; // Handle disabled tools from config or environment const disabledToolsFromEnv = process.env.GH_DISABLED_TOOLS; const disabledToolsFromConfig = config.disabledTools; if (disabledToolsFromEnv || disabledToolsFromConfig) { // If config.disabledTools is already an array, use it directly if (Array.isArray(disabledToolsFromConfig)) { disabledToolsFromConfig.forEach((toolName) => this.disabledTools.add(toolName.trim()) ); } else if (disabledToolsFromEnv) { // Parse from environment variable as comma-separated string disabledToolsFromEnv .split(",") .forEach((toolName) => this.disabledTools.add(toolName.trim())); } } // Handle allowed tools from config or environment const allowedToolsFromEnv = process.env.GH_ALLOWED_TOOLS; const allowedToolsFromConfig = config.allowedTools; if (allowedToolsFromEnv || allowedToolsFromConfig) { this.allowedTools = new Set(); // If config.allowedTools is already an array, use it directly if (Array.isArray(allowedToolsFromConfig)) { allowedToolsFromConfig.forEach((toolName) => this.allowedTools.add(toolName.trim()) ); } else if (allowedToolsFromEnv) { // Parse from environment variable as comma-separated string allowedToolsFromEnv .split(",") .forEach((toolName) => this.allowedTools.add(toolName.trim())); } } // Handle allowed repos from config or environment const allowedReposFromEnv = process.env.GH_ALLOWED_REPOS; const allowedReposFromConfig = config.allowedRepos; if (allowedReposFromEnv || allowedReposFromConfig) { this.allowedRepos = new Set(); // If config.allowedRepos is already an array, use it directly if (Array.isArray(allowedReposFromConfig)) { allowedReposFromConfig.forEach((repo) => this.allowedRepos.add(repo.trim()) ); } else if (allowedReposFromEnv) { // Parse from environment variable as comma-separated string allowedReposFromEnv .split(",") .forEach((repo) => this.allowedRepos.add(repo.trim())); } } this.server = new this.Server( { name: "GitHub Repos Manager MCP Server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); } setDefaultRepo(owner, repo) { // First check if this repo is allowed if (this.allowedRepos) { const repoIdentifier = `${owner}/${repo}`; const isAllowed = this.allowedRepos.has(repoIdentifier) || this.allowedRepos.has(owner) || Array.from(this.allowedRepos).some((allowed) => { if (allowed.includes("/")) { return allowed === repoIdentifier; } return allowed === owner; }); if (!isAllowed) { const allowedList = Array.from(this.allowedRepos).join(", "); return { content: [ { type: "text", text: `Repository ${repoIdentifier} is not in the allowed repositories list. Allowed: ${allowedList}`, }, ], isError: true, }; } } this.defaultOwner = owner; this.defaultRepo = repo; const successMessage = `📦 Default repository set to: ${owner}/${repo}`; console.error(successMessage); return { content: [{ type: "text", text: successMessage }], }; } getHandlerArgs(args) { const effectiveArgs = { ...(args || {}) }; // Apply defaults if not specified if (!effectiveArgs.owner && this.defaultOwner) { effectiveArgs.owner = this.defaultOwner; } if (!effectiveArgs.repo && this.defaultRepo) { effectiveArgs.repo = this.defaultRepo; } // Check if the repository is allowed if (this.allowedRepos && effectiveArgs.owner) { let isAllowed = false; if (effectiveArgs.repo) { // Check full repo path const repoIdentifier = `${effectiveArgs.owner}/${effectiveArgs.repo}`; isAllowed = this.allowedRepos.has(repoIdentifier) || this.allowedRepos.has(effectiveArgs.owner); } else { // Only owner specified, check if owner is allowed isAllowed = this.allowedRepos.has(effectiveArgs.owner) || Array.from(this.allowedRepos).some((allowed) => allowed.startsWith(`${effectiveArgs.owner}/`) ); } if (!isAllowed) { const allowedList = Array.from(this.allowedRepos).join(", "); const attempted = effectiveArgs.repo ? `${effectiveArgs.owner}/${effectiveArgs.repo}` : effectiveArgs.owner; throw new Error( `Repository or owner '${attempted}' is not in the allowed list. Allowed: ${allowedList}` ); } } return effectiveArgs; } setupToolHandlers() { this.server.setRequestHandler(this.ListToolsRequestSchema, async () => { try { const allTools = Object.values(toolsConfig); let availableTools; if (this.allowedTools) { // If allowedTools is set, only those tools are available (disabledTools is ignored) availableTools = allTools.filter((tool) => this.allowedTools.has(tool.name) ); } else { // If allowedTools is not set, all tools except disabled ones are available availableTools = allTools.filter( (tool) => !this.disabledTools.has(tool.name) ); } return { tools: availableTools, }; } catch (error) { const errorResponse = handleError(error, "list_tools"); return { tools: [], error: errorResponse.message, }; } }); this.server.setRequestHandler( this.CallToolRequestSchema, async (request) => { let processedArgs; let name, rawArgs; try { // Validate request structure if (!request || typeof request !== "object") { throw new Error("Invalid request: request must be an object"); } if (!request.params || typeof request.params !== "object") { throw new Error("Invalid request: missing or invalid params"); } name = request.params.name; rawArgs = request.params.arguments; // Validate tool name if (!name || typeof name !== "string" || name.trim() === "") { throw new Error( "Invalid request: tool name is required and must be a non-empty string" ); } name = name.trim(); // Ensure rawArgs is an object (or null) if ( rawArgs !== null && rawArgs !== undefined && typeof rawArgs !== "object" ) { throw new Error( "Invalid request: arguments must be an object, null, or undefined" ); } // Default to empty object if args are not provided rawArgs = rawArgs || {}; // Check if tool is allowed based on configuration if (this.allowedTools) { // If allowedTools is set, only those tools can be used if (!this.allowedTools.has(name)) { throw new Error( `Tool '${name}' is not in the allowed tools list.` ); } } else { // If allowedTools is not set, check disabled tools if (this.disabledTools.has(name)) { throw new Error( `Tool '${name}' is disabled by server configuration.` ); } } // For 'set_default_repo', we don't want to process args through getHandlerArgs // as it might throw an error if the repo is locked and args are different. // The setDefaultRepo method itself handles the locking logic. if (name === "set_default_repo") { if (!rawArgs || !rawArgs.owner || !rawArgs.repo) { throw new Error( "Both owner and repo are required in arguments for set_default_repo" ); } return this.setDefaultRepo(rawArgs.owner, rawArgs.repo); } // Validate API service is available if (!this.api) { throw new Error("GitHub API service is not initialized"); } // For all other tools, process arguments to apply default/locked repo context processedArgs = this.getHandlerArgs(rawArgs); switch (name) { // Repository tools case "list_repos": return await repositoryHandlerFunctions.listRepos( processedArgs, this.api ); case "get_repo_info": return await repositoryHandlerFunctions.getRepoInfo( processedArgs, this.api ); case "search_repos": return await repositoryHandlerFunctions.searchRepos( processedArgs, this.api ); case "get_repo_contents": return await repositoryHandlerFunctions.getRepoContents( processedArgs, this.api ); // set_default_repo is handled above case "list_repo_collaborators": return await repositoryHandlerFunctions.listRepoCollaborators( processedArgs, this.api ); // Issue tools case "list_issues": return await issueHandlerFunctions.listIssues( processedArgs, this.api ); case "create_issue": return await issueHandlerFunctions.createIssue( processedArgs, this.api ); case "edit_issue": return await issueHandlerFunctions.editIssue( processedArgs, this.api ); case "get_issue_details": return await issueHandlerFunctions.getIssueDetails( processedArgs, this.api ); case "lock_issue": return await issueHandlerFunctions.lockIssue( processedArgs, this.api ); case "unlock_issue": return await issueHandlerFunctions.unlockIssue( processedArgs, this.api ); case "add_assignees_to_issue": return await issueHandlerFunctions.addAssigneesToIssue( processedArgs, this.api ); case "remove_assignees_from_issue": return await issueHandlerFunctions.removeAssigneesFromIssue( processedArgs, this.api ); // Comment tools case "list_issue_comments": return await commentHandlerFunctions.listIssueComments( processedArgs, this.api ); case "create_issue_comment": return await commentHandlerFunctions.createIssueComment( processedArgs, this.api ); case "edit_issue_comment": return await commentHandlerFunctions.editIssueComment( processedArgs, this.api ); case "delete_issue_comment": return await commentHandlerFunctions.deleteIssueComment( processedArgs, this.api ); // Pull request tools case "list_prs": return await pullRequestHandlerFunctions.listPRs( processedArgs, this.api ); // Enhanced Pull Request tools case "create_pull_request": return formatHandlerResponse( await this.enhancedPrHandler.create_pull_request( processedArgs, this.api ) ); case "edit_pull_request": return formatHandlerResponse( await this.enhancedPrHandler.edit_pull_request( processedArgs, this.api ) ); case "get_pr_details": return formatHandlerResponse( await this.enhancedPrHandler.get_pr_details( processedArgs, this.api ) ); case "list_pr_reviews": return formatHandlerResponse( await this.enhancedPrHandler.list_pr_reviews( processedArgs, this.api ) ); case "create_pr_review": return formatHandlerResponse( await this.enhancedPrHandler.create_pr_review( processedArgs, this.api ) ); case "list_pr_files": return formatHandlerResponse( await this.enhancedPrHandler.list_pr_files( processedArgs, this.api ) ); // File Management tools case "create_file": return formatHandlerResponse( await this.fileManagementHandler.createFileHandler( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "update_file": return formatHandlerResponse( await this.fileManagementHandler.updateFileHandler( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "upload_file": return formatHandlerResponse( await this.fileManagementHandler.uploadFileHandler( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "delete_file": return formatHandlerResponse( await this.fileManagementHandler.deleteFileHandler( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); // User tools case "get_user_info": return await userHandlerFunctions.getUserInfo( processedArgs, this.api ); // Labels tools case "list_repo_labels": return await labelsHandlerFunctions.listRepoLabels( processedArgs, this.api ); case "create_label": return await labelsHandlerFunctions.createLabel( processedArgs, this.api ); case "edit_label": return await labelsHandlerFunctions.editLabel( processedArgs, this.api ); case "delete_label": return await labelsHandlerFunctions.deleteLabel( processedArgs, this.api ); // Milestones tools case "list_milestones": return await milestonesHandlerFunctions.listMilestones( processedArgs, this.api ); case "create_milestone": return await milestonesHandlerFunctions.createMilestone( processedArgs, this.api ); case "edit_milestone": return await milestonesHandlerFunctions.editMilestone( processedArgs, this.api ); case "delete_milestone": return await milestonesHandlerFunctions.deleteMilestone( processedArgs, this.api ); // Branch and Commit tools case "list_branches": return await branchCommitHandlerFunctions.listBranches( processedArgs, this.api ); case "create_branch": return await branchCommitHandlerFunctions.createBranch( processedArgs, this.api ); case "list_commits": return await branchCommitHandlerFunctions.listCommits( processedArgs, this.api ); case "get_commit_details": return await branchCommitHandlerFunctions.getCommitDetails( processedArgs, this.api ); case "compare_commits": return await branchCommitHandlerFunctions.compareCommits( processedArgs, this.api ); // Security & Access Management tools case "list_deploy_keys": return formatHandlerResponse( await this.securityHandler.list_deploy_keys( processedArgs, this.api ) ); case "create_deploy_key": return formatHandlerResponse( await this.securityHandler.create_deploy_key( processedArgs, this.api ) ); case "delete_deploy_key": return formatHandlerResponse( await this.securityHandler.delete_deploy_key( processedArgs, this.api ) ); case "list_webhooks": return formatHandlerResponse( await this.securityHandler.list_webhooks( processedArgs, this.api ) ); case "create_webhook": return formatHandlerResponse( await this.securityHandler.create_webhook( processedArgs, this.api ) ); case "edit_webhook": return formatHandlerResponse( await this.securityHandler.edit_webhook(processedArgs, this.api) ); case "delete_webhook": return formatHandlerResponse( await this.securityHandler.delete_webhook( processedArgs, this.api ) ); case "list_secrets": return formatHandlerResponse( await this.securityHandler.list_secrets(processedArgs, this.api) ); case "update_secret": return formatHandlerResponse( await this.securityHandler.update_secret( processedArgs, this.api ) ); // Repository Analytics & Insights tools case "get_repo_stats": return formatHandlerResponse( await this.analyticsHandler.get_repo_stats( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_repo_topics": return formatHandlerResponse( await this.analyticsHandler.list_repo_topics( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "update_repo_topics": return formatHandlerResponse( await this.analyticsHandler.update_repo_topics( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "get_repo_languages": return formatHandlerResponse( await this.analyticsHandler.get_repo_languages( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_stargazers": return formatHandlerResponse( await this.analyticsHandler.list_stargazers( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_watchers": return formatHandlerResponse( await this.analyticsHandler.list_watchers( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_forks": return formatHandlerResponse( await this.analyticsHandler.list_forks( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "get_repo_traffic": return formatHandlerResponse( await this.analyticsHandler.get_repo_traffic( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); // Search tools case "search_issues": return formatHandlerResponse( await this.searchHandler.search_issues( this.api.octokit, { owner: this.defaultOwner, repo: this.defaultRepo }, processedArgs, this.api ) ); case "search_commits": return formatHandlerResponse( await this.searchHandler.search_commits( this.api.octokit, { owner: this.defaultOwner, repo: this.defaultRepo }, processedArgs, this.api ) ); case "search_code": return formatHandlerResponse( await this.searchHandler.search_code( this.api.octokit, { owner: this.defaultOwner, repo: this.defaultRepo }, processedArgs, this.api ) ); case "search_users": return formatHandlerResponse( await this.searchHandler.search_users( this.api.octokit, { owner: this.defaultOwner, repo: this.defaultRepo }, processedArgs, this.api ) ); case "search_topics": return formatHandlerResponse( await this.searchHandler.search_topics( this.api.octokit, { owner: this.defaultOwner, repo: this.defaultRepo }, processedArgs, this.api ) ); // Organization tools case "list_org_repos": return formatHandlerResponse( await this.organizationHandler.listOrgRepos( this.api.octokit, processedArgs, this.api ) ); case "list_org_members": return formatHandlerResponse( await this.organizationHandler.listOrgMembers( this.api.octokit, processedArgs, this.api ) ); case "get_org_info": return formatHandlerResponse( await this.organizationHandler.getOrgInfo( this.api.octokit, processedArgs, this.api ) ); case "list_org_teams": return formatHandlerResponse( await this.organizationHandler.listOrgTeams( this.api.octokit, processedArgs, this.api ) ); case "get_team_members": return formatHandlerResponse( await this.organizationHandler.getTeamMembers( this.api.octokit, processedArgs, this.api ) ); case "manage_team_repos": return formatHandlerResponse( await this.organizationHandler.manageTeamRepos( this.api.octokit, processedArgs, this.api ) ); // Projects & Boards tools case "list_repo_projects": return formatHandlerResponse( await this.projectsHandler.list_repo_projects( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "create_project": return formatHandlerResponse( await this.projectsHandler.create_project( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_project_columns": return formatHandlerResponse( await this.projectsHandler.list_project_columns( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "list_project_cards": return formatHandlerResponse( await this.projectsHandler.list_project_cards( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "create_project_card": return formatHandlerResponse( await this.projectsHandler.create_project_card( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "move_project_card": return formatHandlerResponse( await this.projectsHandler.move_project_card( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); // GitHub Actions & Workflows tools case "list_workflows": return formatHandlerResponse( await this.workflowsHandler.list_workflows( processedArgs, this.api ) ); case "list_workflow_runs": return formatHandlerResponse( await this.workflowsHandler.list_workflow_runs( processedArgs, this.api ) ); case "get_workflow_run_details": return formatHandlerResponse( await this.workflowsHandler.get_workflow_run_details( processedArgs, this.api ) ); case "trigger_workflow": return formatHandlerResponse( await this.workflowsHandler.trigger_workflow( processedArgs, this.api ) ); case "download_workflow_artifacts": return formatHandlerResponse( await this.workflowsHandler.download_workflow_artifacts( processedArgs, this.api ) ); case "cancel_workflow_run": return formatHandlerResponse( await this.workflowsHandler.cancel_workflow_run( processedArgs, this.api ) ); // Advanced Features tools case "code_quality_checks": return formatHandlerResponse( await this.advancedFeaturesHandler.handleCodeQualityChecks( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "custom_dashboards": return formatHandlerResponse( await this.advancedFeaturesHandler.handleCustomDashboards( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "automated_reporting": return formatHandlerResponse( await this.advancedFeaturesHandler.handleAutomatedReporting( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "notification_management": return formatHandlerResponse( await this.advancedFeaturesHandler.handleNotificationManagement( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "release_management": return formatHandlerResponse( await this.advancedFeaturesHandler.handleReleaseManagement( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); case "dependency_analysis": return formatHandlerResponse( await this.advancedFeaturesHandler.handleDependencyAnalysis( processedArgs, { owner: this.defaultOwner, repo: this.defaultRepo }, this.api ) ); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { // Ensure we always have a safe fallback error message let errorMessage = "An unexpected error occurred"; let toolName = name || "unknown_tool"; try { // Use enhanced error handling const errorResponse = handleError(error, `tool:${toolName}`, { toolName: toolName, arguments: rawArgs, processedArgs: processedArgs, }); errorMessage = errorResponse.message; } catch (handlerError) { // If even the error handler fails, use a simple message errorMessage = `Error in ${toolName}: ${ error?.message || "Unknown error occurred" }`; // Log to stderr to avoid MCP protocol interference process.stderr.write( `Error handler failed: ${handlerError.message}\n` ); process.stderr.write( `Original error: ${error?.message || "Unknown"}\n` ); } return { content: [ { type: "text", text: errorMessage, }, ], isError: true, }; } } ); } async run() { let authenticatedUser; try { authenticatedUser = await this.api.testAuthentication(); } catch (error) { console.error("Failed to authenticate with GitHub:", error.message); process.exit(1); } const transport = new this.StdioServerTransport(); try { await this.server.connect(transport); console.error("🚀 GitHub Repos Manager MCP Server is running..."); console.error( `👤 Authenticated as: ${authenticatedUser.login} (${ authenticatedUser.name || "No name set" }) ${ authenticatedUser.email ? "email: " + authenticatedUser.email : "" }` ); const totalToolsCount = Object.keys(toolsConfig).length; let availableToolsCount; // Show default repository if set if (this.defaultOwner && this.defaultRepo) { console.error( `📦 Default repository: ${this.defaultOwner}/${this.defaultRepo}` ); } // Show allowed repositories if restricted if (this.allowedRepos) { const allowedReposList = Array.from(this.allowedRepos).join(", "); console.error( `🔒 Repository operations restricted to: ${allowedReposList}` ); } if (this.allowedTools) { // When allowedTools is set, only those tools are available availableToolsCount = this.allowedTools.size; const allowedToolNames = Array.from(this.allowedTools).join(", "); console.error( `${availableToolsCount} out of ${totalToolsCount} tools are available (allowed tools only).` ); console.error( `✅ Allowed tools (${availableToolsCount}): ${allowedToolNames}` ); } else { // When allowedTools is not set, all tools except disabled ones are available const disabledToolsCount = this.disabledTools.size; availableToolsCount = totalToolsCount - disabledToolsCount; if (disabledToolsCount === 0) { console.error(`✅ All ${totalToolsCount} tools are available.`); } else { console.error( `✅ ${availableToolsCount} out of ${totalToolsCount} tools are available.` ); const disabledToolNames = Array.from(this.disabledTools).join(", "); console.error( `❌ Disabled tools (${disabledToolsCount}): ${disabledToolNames}` ); } } process.stdin.resume(); } catch (error) { console.error("Failed to connect server:", error); process.exit(1); } } } // Global error handlers to prevent server crashes process.on("uncaughtException", (error) => { process.stderr.write(`Uncaught Exception: ${error.message}\n`); process.stderr.write(`Stack: ${error.stack}\n`); // Don't exit on uncaught exceptions - try to continue running }); process.on("unhandledRejection", (reason, promise) => { process.stderr.write( `Unhandled Rejection at: ${promise}, reason: ${reason}\n` ); // Don't exit on unhandled rejections - try to continue running }); process.on("SIGINT", async () => { console.error("🛑 Shutting down GitHub Repos Manager MCP Server..."); process.exit(0); }); process.on("SIGTERM", async () => { console.error( "⚠️ Received SIGTERM, shutting down GitHub Repos Manager MCP Server..." ); process.exit(0); }); module.exports = GitHubMCPServer;

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