mcp-github

  • src
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import * as repository from './operations/repository.js'; import * as files from './operations/files.js'; import * as issues from './operations/issues.js'; import * as pulls from './operations/pulls.js'; import * as branches from './operations/branches.js'; import * as search from './operations/search.js'; import * as commits from './operations/commits.js'; import * as releases from './operations/releases.js'; import * as statuses from './operations/statuses.js'; import * as rate_limit from './operations/rate_limit.js'; import * as gists from './operations/gists.js'; import * as projects from './operations/projects.js'; import * as packages from './operations/packages.js'; import { GitHubError, GitHubValidationError, GitHubResourceNotFoundError, GitHubAuthenticationError, GitHubPermissionError, GitHubRateLimitError, GitHubConflictError, isGitHubError, } from './common/errors.js'; import { VERSION } from "./common/version.js"; // const CallToolRequestPATSchema = CallToolRequestSchema.extend({ // github_pat: z.string().describe("GitHub Personal Access Token"), // }); const server = new Server( { name: "mcp-github", version: VERSION, }, { capabilities: { tools: {}, }, } ); function formatGitHubError(error: GitHubError): string { let message = `GitHub API Error: ${error.message}`; if (error instanceof GitHubValidationError) { message = `Validation Error: ${error.message}`; if (error.response) { message += `\nDetails: ${JSON.stringify(error.response)}`; } } else if (error instanceof GitHubResourceNotFoundError) { message = `Not Found: ${error.message}`; } else if (error instanceof GitHubAuthenticationError) { message = `Authentication Failed: ${error.message}`; } else if (error instanceof GitHubPermissionError) { message = `Permission Denied: ${error.message}`; } else if (error instanceof GitHubRateLimitError) { message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`; } else if (error instanceof GitHubConflictError) { message = `Conflict: ${error.message}`; } return message; } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_or_update_file", description: "Create or update a single file in a GitHub repository", inputSchema: zodToJsonSchema(files.CreateOrUpdateFileSchema), }, { name: "search_repositories", description: "Search for GitHub repositories", inputSchema: zodToJsonSchema(repository.SearchRepositoriesSchema), }, { name: "create_repository", description: "Create a new GitHub repository in your account", inputSchema: zodToJsonSchema(repository.CreateRepositoryOptionsSchema), }, { name: "get_file_contents", description: "Get the contents of a file or directory from a GitHub repository", inputSchema: zodToJsonSchema(files.GetFileContentsSchema), }, { name: "push_files", description: "Push multiple files to a GitHub repository in a single commit", inputSchema: zodToJsonSchema(files.PushFilesSchema), }, { name: "create_issue", description: "Create a new issue in a GitHub repository", inputSchema: zodToJsonSchema(issues.CreateIssueSchema), }, { name: "create_pull_request", description: "Create a new pull request in a GitHub repository", inputSchema: zodToJsonSchema(pulls.CreatePullRequestSchema), }, { name: "fork_repository", description: "Fork a GitHub repository to your account or specified organization", inputSchema: zodToJsonSchema(repository.ForkRepositorySchema), }, { name: "create_branch", description: "Create a new branch in a GitHub repository", inputSchema: zodToJsonSchema(branches.CreateBranchSchema), }, { name: "list_commits", description: "Get list of commits of a branch in a GitHub repository", inputSchema: zodToJsonSchema(commits.ListCommitsSchema) }, { name: "list_issues", description: "List issues in a GitHub repository with filtering options", inputSchema: zodToJsonSchema(issues.ListIssuesOptionsSchema) }, { name: "update_issue", description: "Update an existing issue in a GitHub repository", inputSchema: zodToJsonSchema(issues.UpdateIssueOptionsSchema) }, { name: "add_issue_comment", description: "Add a comment to an existing issue", inputSchema: zodToJsonSchema(issues.IssueCommentSchema) }, { name: "search_code", description: "Search for code across GitHub repositories", inputSchema: zodToJsonSchema(search.SearchCodeSchema), }, { name: "search_issues", description: "Search for issues and pull requests across GitHub repositories", inputSchema: zodToJsonSchema(search.SearchIssuesSchema), }, { name: "search_users", description: "Search for users on GitHub", inputSchema: zodToJsonSchema(search.SearchUsersSchema), }, { name: "get_issue", description: "Get details of a specific issue in a GitHub repository.", inputSchema: zodToJsonSchema(issues.GetIssueSchema) }, // Releases and Tags { name: "create_release", description: "Create a new release in a GitHub repository", inputSchema: zodToJsonSchema(releases.CreateReleaseSchema), }, { name: "list_releases", description: "List releases for a GitHub repository", inputSchema: zodToJsonSchema(releases.ListReleasesSchema), }, { name: "delete_release", description: "Delete a release from a GitHub repository", inputSchema: zodToJsonSchema(releases.DeleteReleaseSchema), }, { name: "get_release_asset", description: "Get a release asset from a GitHub repository", inputSchema: zodToJsonSchema(releases.GetReleaseAssetSchema), }, { name: "upload_release_asset", description: "Upload an asset to a GitHub release", inputSchema: zodToJsonSchema(releases.UploadReleaseAssetSchema), }, { name: "create_tag", description: "Create a new tag in a GitHub repository", inputSchema: zodToJsonSchema(releases.CreateTagSchema), }, // Pull Request Reviews { name: "create_pull_request_review", description: "Create a review for a pull request", inputSchema: zodToJsonSchema(pulls.CreatePullRequestReviewSchema), }, { name: "submit_pull_request_review", description: "Submit a pull request review (approve, request changes, or comment)", inputSchema: zodToJsonSchema(pulls.SubmitPullRequestReviewSchema), }, { name: "dismiss_pull_request_review", description: "Dismiss a pull request review", inputSchema: zodToJsonSchema(pulls.DismissPullRequestReviewSchema), }, // Statuses and Checks { name: "create_commit_status", description: "Create a status for a commit (build passed/failed, etc.)", inputSchema: zodToJsonSchema(statuses.CreateCommitStatusSchema), }, { name: "get_commit_statuses", description: "Get statuses for a commit", inputSchema: zodToJsonSchema(statuses.GetCommitStatusesSchema), }, { name: "get_combined_status", description: "Get the combined status for a commit", inputSchema: zodToJsonSchema(statuses.GetCombinedStatusSchema), }, // Rate Limit Info { name: "get_rate_limit", description: "Check the current rate limit status", inputSchema: zodToJsonSchema(rate_limit.GetRateLimitSchema), }, // Gists { name: "create_gist", description: "Create a new gist", inputSchema: zodToJsonSchema(gists.CreateGistSchema), }, { name: "list_gists", description: "List gists for the authenticated user", inputSchema: zodToJsonSchema(gists.ListGistsSchema), }, { name: "get_gist", description: "Get a specific gist", inputSchema: zodToJsonSchema(gists.GetGistSchema), }, // Project Boards { name: "list_projects", description: "List projects for a repository", inputSchema: zodToJsonSchema(projects.ListProjectsSchema), }, { name: "create_project", description: "Create a new project for a repository", inputSchema: zodToJsonSchema(projects.CreateProjectSchema), }, { name: "list_project_columns", description: "List columns for a project", inputSchema: zodToJsonSchema(projects.ListProjectColumnsSchema), }, { name: "create_project_column", description: "Create a new column for a project", inputSchema: zodToJsonSchema(projects.CreateProjectColumnSchema), }, { name: "create_project_card", description: "Create a new card in a project column", inputSchema: zodToJsonSchema(projects.CreateProjectCardSchema), }, // Packages { name: "list_org_packages", description: "List packages for an organization", inputSchema: zodToJsonSchema(packages.ListOrgPackagesSchema), }, { name: "list_user_packages", description: "List packages for a user", inputSchema: zodToJsonSchema(packages.ListUserPackagesSchema), }, { name: "list_repo_packages", description: "List packages for a repository", inputSchema: zodToJsonSchema(packages.ListRepoPackagesSchema), }, { name: "get_org_package", description: "Get a package for an organization", inputSchema: zodToJsonSchema(packages.GetOrgPackageSchema), }, { name: "get_user_package", description: "Get a package for a user", inputSchema: zodToJsonSchema(packages.GetUserPackageSchema), }, { name: "get_repo_package", description: "Get a package for a repository", inputSchema: zodToJsonSchema(packages.GetRepoPackageSchema), }, // Pull Request Diff { name: "get_pull_request_diff", description: "Get the diff for a pull request", inputSchema: zodToJsonSchema(pulls.GetPullRequestDiffSchema), }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { params } = request; if (!params.arguments) { throw new Error("Arguments are required"); } if (!params.arguments.github_pat) { throw new Error("GitHub PAT is required"); } switch (params.name) { case "fork_repository": { const args = repository._ForkRepositorySchema.parse(params.arguments); const fork = await repository.forkRepository(args.github_pat, args.owner, args.repo, args.organization); return { content: [{ type: "text", text: JSON.stringify(fork, null, 2) }], }; } case "create_branch": { const args = branches._CreateBranchSchema.parse(params.arguments); const branch = await branches.createBranchFromRef( args.github_pat, args.owner, args.repo, args.branch, args.from_branch ); return { content: [{ type: "text", text: JSON.stringify(branch, null, 2) }], }; } case "search_repositories": { const args = repository._SearchRepositoriesSchema.parse(params.arguments); const results = await repository.searchRepositories( args.github_pat, args.query, args.page, args.perPage ); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "create_repository": { const argsWithPat = repository._CreateRepositoryOptionsSchema.parse(params.arguments); const { github_pat, ...args } = argsWithPat; const result = await repository.createRepository(github_pat, args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_file_contents": { const args = files._GetFileContentsSchema.parse(params.arguments); const contents = await files.getFileContents(args); let text = ''; if (Array.isArray(contents)) { // this means it's a directory text = `Directory Contents:\n${contents.map(c => `- ${c.path} (${c.type}, ${c.type === 'file' ? c.size.toString() + ' bytes' : ''})`).join('\n')}` } else { // this means it's a singular file text = `File Name: ${contents.name} File Path: ${contents.path} File SHA: ${contents.sha} File Size: ${contents.size} File URL: ${contents.url} File HTML URL: ${contents.html_url} File Download URL: ${contents.download_url} File Type: ${contents.type} File Encoding: ${contents.encoding} File Content:\n\`\`\`${contents.content}\n\`\`\`` } return { content: [{ type: "text", text }], }; } case "create_or_update_file": { const args = files._CreateOrUpdateFileSchema.parse(params.arguments); const result = await files.createOrUpdateFile( args.github_pat, args.owner, args.repo, args.path, args.content, args.message, args.branch, args.sha ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "push_files": { const args = files._PushFilesSchema.parse(params.arguments); const result = await files.pushFiles( args.github_pat, args.owner, args.repo, args.branch, args.files, args.message ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "create_issue": { const args = issues._CreateIssueSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const issue = await issues.createIssue(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }], }; } case "create_pull_request": { const argsWithPat = pulls._CreatePullRequestSchema.parse(params.arguments); const { github_pat, ...args } = argsWithPat; const pullRequest = await pulls.createPullRequest(github_pat, args); return { content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }], }; } case "search_code": { const argsWithPat = search._SearchCodeSchema.parse(params.arguments); const { github_pat, ...args } = argsWithPat; const results = await search.searchCode(github_pat, args); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "search_issues": { const argsWithPat = search._SearchIssuesSchema.parse(params.arguments); const { github_pat, ...args } = argsWithPat; const results = await search.searchIssues(github_pat, args); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "search_users": { const argsWithPat = search._SearchUsersSchema.parse(params.arguments); const { github_pat, ...args } = argsWithPat; const results = await search.searchUsers(github_pat, args); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "list_issues": { const args = issues._ListIssuesOptionsSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const result = await issues.listIssues(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "update_issue": { const argsWithPat = issues._UpdateIssueOptionsSchema.parse(params.arguments); const { github_pat, owner, repo, issue_number, ...options } = argsWithPat; const result = await issues.updateIssue(github_pat, owner, repo, issue_number, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "add_issue_comment": { const argsWithPat = issues._IssueCommentSchema.parse(params.arguments); const { github_pat, owner, repo, issue_number, body } = argsWithPat; const result = await issues.addIssueComment(github_pat, owner, repo, issue_number, body); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_commits": { const args = commits._ListCommitsSchema.parse(params.arguments); const results = await commits.listCommits( args.github_pat, args.owner, args.repo, args.page, args.perPage, args.sha ); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; } case "get_issue": { const args = issues._GetIssueSchema.parse(params.arguments); const issue = await issues.getIssue(args.github_pat, args.owner, args.repo, args.issue_number); return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }], }; } // Releases and Tags case "create_release": { const args = releases._CreateReleaseSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const result = await releases.createRelease(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_releases": { const args = releases._ListReleasesSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const result = await releases.listReleases(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "delete_release": { const args = releases._DeleteReleaseSchema.parse(params.arguments); await releases.deleteRelease(args.github_pat, args.owner, args.repo, args.release_id); return { content: [{ type: "text", text: JSON.stringify({ success: true }, null, 2) }], }; } case "get_release_asset": { const args = releases._GetReleaseAssetSchema.parse(params.arguments); const result = await releases.getReleaseAsset(args.github_pat, args.owner, args.repo, args.asset_id); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "upload_release_asset": { const args = releases._UploadReleaseAssetSchema.parse(params.arguments); const { github_pat, owner, repo, release_id, name, content, content_type, label } = args; const result = await releases.uploadReleaseAsset( github_pat, owner, repo, release_id, name, content, content_type, label ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "create_tag": { const args = releases._CreateTagSchema.parse(params.arguments); const { github_pat, owner, repo, ref, sha } = args; const result = await releases.createTag(github_pat, owner, repo, ref, sha); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Pull Request Reviews case "create_pull_request_review": { const args = pulls._CreatePullRequestReviewSchema.parse(params.arguments); const { github_pat, owner, repo, pull_number, ...options } = args; const result = await pulls.createPullRequestReview(github_pat, owner, repo, pull_number, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "submit_pull_request_review": { const args = pulls._SubmitPullRequestReviewSchema.parse(params.arguments); const { github_pat, owner, repo, pull_number, review_id, event, body } = args; const result = await pulls.submitPullRequestReview( github_pat, owner, repo, pull_number, review_id, event, body ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "dismiss_pull_request_review": { const args = pulls._DismissPullRequestReviewSchema.parse(params.arguments); const { github_pat, owner, repo, pull_number, review_id, message } = args; const result = await pulls.dismissPullRequestReview( github_pat, owner, repo, pull_number, review_id, message ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Statuses and Checks case "create_commit_status": { const args = statuses._CreateCommitStatusSchema.parse(params.arguments); const { github_pat, owner, repo, sha, state, ...options } = args; const result = await statuses.createCommitStatus(github_pat, owner, repo, sha, state, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_commit_statuses": { const args = statuses._GetCommitStatusesSchema.parse(params.arguments); const { github_pat, owner, repo, ref } = args; const result = await statuses.getCommitStatuses(github_pat, owner, repo, ref); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_combined_status": { const args = statuses._GetCombinedStatusSchema.parse(params.arguments); const { github_pat, owner, repo, ref } = args; const result = await statuses.getCombinedStatus(github_pat, owner, repo, ref); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Rate Limit Info case "get_rate_limit": { const args = rate_limit._GetRateLimitSchema.parse(params.arguments); const result = await rate_limit.getRateLimit(args.github_pat); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Gists case "create_gist": { const args = gists._CreateGistSchema.parse(params.arguments); const { github_pat, description, public: isPublic, files } = args; const result = await gists.createGist(github_pat, description, isPublic, files); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_gists": { const args = gists._ListGistsSchema.parse(params.arguments); const { github_pat, ...options } = args; const result = await gists.listGists(github_pat, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_gist": { const args = gists._GetGistSchema.parse(params.arguments); const { github_pat, gist_id } = args; const result = await gists.getGist(github_pat, gist_id); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Project Boards case "list_projects": { const args = projects._ListProjectsSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const result = await projects.listProjects(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "create_project": { const args = projects._CreateProjectSchema.parse(params.arguments); const { github_pat, owner, repo, name, body } = args; const result = await projects.createProject(github_pat, owner, repo, name, body); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_project_columns": { const args = projects._ListProjectColumnsSchema.parse(params.arguments); const { github_pat, project_id, ...options } = args; const result = await projects.listProjectColumns(github_pat, project_id, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "create_project_column": { const args = projects._CreateProjectColumnSchema.parse(params.arguments); const { github_pat, project_id, name } = args; const result = await projects.createProjectColumn(github_pat, project_id, name); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "create_project_card": { const args = projects._CreateProjectCardSchema.parse(params.arguments); const { github_pat, column_id, note } = args; const result = await projects.createProjectCard(github_pat, column_id, note); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Packages case "list_org_packages": { const args = packages._ListOrgPackagesSchema.parse(params.arguments); const { github_pat, org, ...options } = args; const result = await packages.listOrgPackages(github_pat, org, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_user_packages": { const args = packages._ListUserPackagesSchema.parse(params.arguments); const { github_pat, username, ...options } = args; const result = await packages.listUserPackages(github_pat, username, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "list_repo_packages": { const args = packages._ListRepoPackagesSchema.parse(params.arguments); const { github_pat, owner, repo, ...options } = args; const result = await packages.listRepoPackages(github_pat, owner, repo, options); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_org_package": { const args = packages._GetOrgPackageSchema.parse(params.arguments); const { github_pat, org, package_type, package_name } = args; const result = await packages.getOrgPackage(github_pat, org, package_type, package_name); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_user_package": { const args = packages._GetUserPackageSchema.parse(params.arguments); const { github_pat, username, package_type, package_name } = args; const result = await packages.getUserPackage(github_pat, username, package_type, package_name); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } case "get_repo_package": { const args = packages._GetRepoPackageSchema.parse(params.arguments); const { github_pat, owner, repo, package_type, package_name } = args; const result = await packages.getRepoPackage(github_pat, owner, repo, package_type, package_name); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } // Pull Request Diff case "get_pull_request_diff": { const args = pulls._GetPullRequestDiffSchema.parse(params.arguments); const { github_pat, owner, repo, pull_number } = args; const result = await pulls.getPullRequestDiff(github_pat, owner, repo, pull_number); return { content: [{ type: "text", text: result }], }; } default: throw new Error(`Unknown tool: ${params.name}`); } } catch (error) { if (error instanceof z.ZodError) { throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`); } if (isGitHubError(error)) { throw new Error(formatGitHubError(error)); } throw error; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("GitHub MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });