diff --git a/README.md b/README.md index 7b9e20fc..3e07e7ef 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,67 @@ automation and interaction capabilities for developers and tools. 1. To run the server in a container, you will need to have [Docker](https://www.docker.com/) installed. 2. Once Docker is installed, you will also need to ensure Docker is running. The image is public; if you get errors on pull, you may have an expired token and need to `docker logout ghcr.io`. -3. Lastly you will need to [Create a GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new). -The MCP server can use many of the GitHub APIs, so enable the permissions that you feel comfortable granting your AI tools (to learn more about access tokens, please check out the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)). +3. [Create a GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new). + Each tool requires specific permissions to function. See the [Required Token Permissions](#required-token-permissions) section below for details. + +## Required Token Permissions + +Each tool requires specific GitHub Personal Access Token permissions to function. Below are the required permissions for each tool category: + +### Users +- **get_me** + - Required permissions: + - `read:user` - Read access to profile info + +### Issues +- **get_issue**, **get_issue_comments**, **list_issues** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + +- **create_issue**, **add_issue_comment**, **update_issue** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + - `write:discussion` - Write access to repository discussions (if using discussions) + +### Pull Requests +- **get_pull_request**, **list_pull_requests**, **get_pull_request_files**, **get_pull_request_status** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + +- **merge_pull_request**, **update_pull_request_branch**, **create_pull_request**, **update_pull_request** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + - `write:discussion` - Write access to repository discussions (if using discussions) + +### Repositories +- **get_file_contents**, **search_repositories**, **list_commits** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + +- **create_or_update_file**, **push_files**, **create_repository**, **fork_repository**, **create_branch** + - Required permissions: + - `repo` - Full control of private repositories (for private repos) + - `public_repo` - Access public repositories (for public repos) + - `delete_repo` - Delete repositories (if needed) + +### Search +- **search_code**, **search_users** + - Required permissions: + - No special permissions required for public data + - `repo` - Required for searching private repositories + +### Code Scanning +- **get_code_scanning_alert**, **list_code_scanning_alerts** + - Required permissions: + - `security_events` - Read and write security events + - `repo` - Full control of private repositories (for private repos) + +Note: For organization repositories, additional organization-specific permissions may be required. ## Installation @@ -476,12 +535,6 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `branch`: Branch name (string, optional) - `sha`: File SHA if updating (string, optional) -- **list_branches** - List branches in a GitHub repository - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `page`: Page number (number, optional) - - `perPage`: Results per page (number, optional) - - **push_files** - Push multiple files in a single commit - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -519,7 +572,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `branch`: New branch name (string, required) - `sha`: SHA to create branch from (string, required) -- **list_commits** - Get a list of commits of a branch in a repository +- **list_commits** - Gets commits of a branch in a repository - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `sha`: Branch name, tag, or commit SHA (string, optional) @@ -534,6 +587,8 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `page`: Page number, for files in the commit (number, optional) - `perPage`: Results per page, for files in the commit (number, optional) +### Search + - **search_code** - Search for code across GitHub repositories - `query`: Search query (string, required) - `sort`: Sort field (string, optional) @@ -674,3 +729,4 @@ The exported Go API of this module should currently be considered unstable, and ## License This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE) for the full terms. + diff --git a/github-mcp-server b/github-mcp-server new file mode 100755 index 00000000..464a0bd3 Binary files /dev/null and b/github-mcp-server differ diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index ba9c6bc2..f93d5f85 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -414,11 +414,11 @@ const ( ) // ManageRepositoryNotificationSubscription creates a tool to manage a repository notification subscription (ignore, watch, delete) -func ManageRepositoryNotificationSubscription(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool("manage_repository_notification_subscription", - mcp.WithDescription(t("TOOL_MANAGE_REPOSITORY_NOTIFICATION_SUBSCRIPTION_DESCRIPTION", "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.")), +func RepoNotificationSub(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("repo_notification_sub", + mcp.WithDescription(t("TOOL_REPO_NOTIFICATION_SUB_DESCRIPTION", "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.")), mcp.WithToolAnnotation(mcp.ToolAnnotation{ - Title: t("TOOL_MANAGE_REPOSITORY_NOTIFICATION_SUBSCRIPTION_USER_TITLE", "Manage repository notification subscription"), + Title: t("TOOL_REPO_NOTIFICATION_SUB_USER_TITLE", "Manage repository notification subscription"), ReadOnlyHint: toBoolPtr(false), }), mcp.WithString("owner", diff --git a/pkg/github/notifications_test.go b/pkg/github/notifications_test.go index 66400295..7e00cdc2 100644 --- a/pkg/github/notifications_test.go +++ b/pkg/github/notifications_test.go @@ -279,11 +279,11 @@ func Test_ManageNotificationSubscription(t *testing.T) { } } -func Test_ManageRepositoryNotificationSubscription(t *testing.T) { +func Test_RepoNotificationSub(t *testing.T) { // Verify tool definition and schema mockClient := github.NewClient(nil) - tool, _ := ManageRepositoryNotificationSubscription(stubGetClientFn(mockClient), translations.NullTranslationHelper) - assert.Equal(t, "manage_repository_notification_subscription", tool.Name) + tool, _ := RepoNotificationSub(stubGetClientFn(mockClient), translations.NullTranslationHelper) + assert.Equal(t, "repo_notification_sub", tool.Name) assert.NotEmpty(t, tool.Description) assert.Contains(t, tool.InputSchema.Properties, "owner") assert.Contains(t, tool.InputSchema.Properties, "repo") @@ -396,7 +396,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { client := github.NewClient(tc.mockedClient) - _, handler := ManageRepositoryNotificationSubscription(stubGetClientFn(client), translations.NullTranslationHelper) + _, handler := RepoNotificationSub(stubGetClientFn(client), translations.NullTranslationHelper) request := createMCPRequest(tc.requestArgs) result, err := handler(context.Background(), request) diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index d6dd3f96..d8757cbf 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -1048,11 +1048,11 @@ func CreatePendingPullRequestReview(getGQLClient GetGQLClientFn, t translations. } // AddPullRequestReviewCommentToPendingReview creates a tool to add a comment to a pull request review. -func AddPullRequestReviewCommentToPendingReview(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { - return mcp.NewTool("add_pull_request_review_comment_to_pending_review", - mcp.WithDescription(t("TOOL_ADD_PULL_REQUEST_REVIEW_COMMENT_TO_PENDING_REVIEW_DESCRIPTION", "Add a comment to the requester's latest pending pull request review, a pending review needs to already exist to call this (check with the user if not sure). If you are using the LINE subjectType, use the get_line_number_in_pull_request_file tool to get an exact line number before commenting.")), +func PrReviewCommentPending(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { + return mcp.NewTool("pr_review_comment_pending", + mcp.WithDescription(t("TOOL_PR_REVIEW_COMMENT_PENDING_DESCRIPTION", "Add a comment to the requester's latest pending pull request review, a pending review needs to already exist to call this (check with the user if not sure). If you are using the LINE subjectType, use the get_line_number_in_pull_request_file tool to get an exact line number before commenting.")), mcp.WithToolAnnotation(mcp.ToolAnnotation{ - Title: t("TOOL_ADD_PULL_REQUEST_REVIEW_COMMENT_TO_PENDING_REVIEW_USER_TITLE", "Add comment to the requester's latest pending pull request review"), + Title: t("TOOL_PR_REVIEW_COMMENT_PENDING_USER_TITLE", "Add comment to the requester's latest pending pull request review"), ReadOnlyHint: toBoolPtr(false), }), // Ideally, for performance sake this would just accept the pullRequestReviewID. However, we would need to diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 6202ec16..ad3ccab3 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -1837,14 +1837,14 @@ func TestCreatePendingPullRequestReview(t *testing.T) { } } -func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { +func TestPrReviewCommentPending(t *testing.T) { t.Parallel() // Verify tool definition once mockClient := githubv4.NewClient(nil) - tool, _ := AddPullRequestReviewCommentToPendingReview(stubGetGQLClientFn(mockClient), translations.NullTranslationHelper) + tool, _ := PrReviewCommentPending(stubGetGQLClientFn(mockClient), translations.NullTranslationHelper) - assert.Equal(t, "add_pull_request_review_comment_to_pending_review", tool.Name) + assert.Equal(t, "pr_review_comment_pending", tool.Name) assert.NotEmpty(t, tool.Description) assert.Contains(t, tool.InputSchema.Properties, "owner") assert.Contains(t, tool.InputSchema.Properties, "repo") @@ -1926,7 +1926,7 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { // Setup client with mock client := githubv4.NewClient(tc.mockedClient) - _, handler := AddPullRequestReviewCommentToPendingReview(stubGetGQLClientFn(client), translations.NullTranslationHelper) + _, handler := PrReviewCommentPending(stubGetGQLClientFn(client), translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 9c1ab34a..9186c4b4 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -77,9 +77,9 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, // Reviews toolsets.NewServerTool(CreateAndSubmitPullRequestReview(getGQLClient, t)), toolsets.NewServerTool(CreatePendingPullRequestReview(getGQLClient, t)), - toolsets.NewServerTool(AddPullRequestReviewCommentToPendingReview(getGQLClient, t)), toolsets.NewServerTool(SubmitPendingPullRequestReview(getGQLClient, t)), toolsets.NewServerTool(DeletePendingPullRequestReview(getGQLClient, t)), + toolsets.NewServerTool(PrReviewCommentPending(getGQLClient, t)), ) codeSecurity := toolsets.NewToolset("code_security", "Code security related tools, such as GitHub Code Scanning"). AddReadTools( @@ -101,7 +101,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, toolsets.NewServerTool(DismissNotification(getClient, t)), toolsets.NewServerTool(MarkAllNotificationsRead(getClient, t)), toolsets.NewServerTool(ManageNotificationSubscription(getClient, t)), - toolsets.NewServerTool(ManageRepositoryNotificationSubscription(getClient, t)), + toolsets.NewServerTool(RepoNotificationSub(getClient, t)), ) // Keep experiments alive so the system doesn't error out when it's always enabled