Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package github-mcp-server for openSUSE:Factory checked in at 2026-09-17 15:21:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/github-mcp-server (Old) and /work/SRC/openSUSE:Factory/.github-mcp-server.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "github-mcp-server" Thu Sep 17 15:21:25 2026 rev:8 rq:1378404 version:1.12.2 Changes: -------- --- /work/SRC/openSUSE:Factory/github-mcp-server/github-mcp-server.changes 2026-09-09 16:22:58.200577236 +0200 +++ /work/SRC/openSUSE:Factory/.github-mcp-server.new.383539/github-mcp-server.changes 2026-09-17 15:23:06.850275456 +0200 @@ -1,0 +2,11 @@ +Wed Sep 16 14:58:05 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 1.12.2: + * Add update_issue_comment tool + * Add remove_issue_reaction, remove_issue_comment_reaction + and remove_pull_request_review_comment_reaction to the + granular issues and pull-requests toolsets +- Vendored Go modules are unchanged (go.mod/go.sum identical + to 1.12.1). + +------------------------------------------------------------------- Old: ---- github-mcp-server-1.12.1.tar.zst New: ---- github-mcp-server-1.12.2.tar.zst ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ github-mcp-server.spec ++++++ --- /var/tmp/diff_new_pack.SVfH9O/_old 2026-09-17 15:23:07.639308514 +0200 +++ /var/tmp/diff_new_pack.SVfH9O/_new 2026-09-17 15:23:07.640308556 +0200 @@ -23,7 +23,7 @@ # -modcacherw leave the module cache writable so cleanup cannot fail %define goflags "-buildmode=pie -mod=vendor -modcacherw" Name: github-mcp-server -Version: 1.12.1 +Version: 1.12.2 Release: 0 Summary: Model Context Protocol server for GitHub License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.SVfH9O/_old 2026-09-17 15:23:07.704311237 +0200 +++ /var/tmp/diff_new_pack.SVfH9O/_new 2026-09-17 15:23:07.715311698 +0200 @@ -3,8 +3,8 @@ <param name="url">https://github.com/github/github-mcp-server</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.12.1</param> - <param name="version">1.12.1</param> + <param name="revision">v1.12.2</param> + <param name="version">1.12.2</param> <param name="changesgenerate">disable</param> </service> <service name="recompress" mode="manual"> @@ -13,7 +13,7 @@ </service> <service name="go_modules" mode="manual"> <param name="compression">zst</param> - <param name="archive">github-mcp-server-1.12.1.tar.zst</param> + <param name="archive">github-mcp-server-1.12.2.tar.zst</param> </service> </services> ++++++ github-mcp-server-1.12.1.tar.zst -> github-mcp-server-1.12.2.tar.zst ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/README.md new/github-mcp-server-1.12.2/README.md --- old/github-mcp-server-1.12.1/README.md 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/README.md 2026-09-16 11:58:29.000000000 +0200 @@ -1057,6 +1057,13 @@ - `repo`: Repository name (string, required) - `sub_issue_id`: The ID of the sub-issue to add. ID is not the same as issue number (number, required) +- **update_issue_comment** - Update issue comment + - **OAuth Challenge Scopes**: `repo` + - `body`: New comment content (string, required) + - `comment_id`: The numeric ID of the issue or pull request conversation comment to update. Do not use a pull request review comment ID. (integer, required) + - `owner`: Repository owner (string, required) + - `repo`: Repository name (string, required) + </details> <details> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/docs/feature-flags.md new/github-mcp-server-1.12.2/docs/feature-flags.md --- old/github-mcp-server-1.12.1/docs/feature-flags.md 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/docs/feature-flags.md 2026-09-16 11:58:29.000000000 +0200 @@ -184,6 +184,20 @@ - `repo`: Repository name (string, required) - `title`: Issue title (string, required) +- **remove_issue_comment_reaction** - Remove Reaction from Issue or Pull Request Comment + - **OAuth Challenge Scopes**: `repo` + - `comment_id`: The issue or pull request comment ID (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `reaction_id`: The reaction ID to remove (number, required) + - `repo`: Repository name (string, required) + +- **remove_issue_reaction** - Remove Reaction from Issue or Pull Request + - **OAuth Challenge Scopes**: `repo` + - `issue_number`: The issue number (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `reaction_id`: The reaction ID to remove (number, required) + - `repo`: Repository name (string, required) + - **remove_sub_issue** - Remove Sub-Issue - **OAuth Challenge Scopes**: `repo` - `issue_number`: The parent issue number (number, required) @@ -301,6 +315,13 @@ - `pullNumber`: The pull request number (number, required) - `repo`: Repository name (string, required) +- **remove_pull_request_review_comment_reaction** - Remove Pull Request Review Comment Reaction + - **OAuth Challenge Scopes**: `repo` + - `comment_id`: The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...). (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `reaction_id`: The reaction ID to remove (number, required) + - `repo`: Repository name (string, required) + - **request_pull_request_reviewers** - Request Pull Request Reviewers - **OAuth Challenge Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_issue_comment_reaction.snap new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_issue_comment_reaction.snap --- old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_issue_comment_reaction.snap 1970-01-01 01:00:00.000000000 +0100 +++ new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_issue_comment_reaction.snap 2026-09-16 11:58:29.000000000 +0200 @@ -0,0 +1,40 @@ +{ + "annotations": { + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Remove Reaction from Issue or Pull Request Comment" + }, + "description": "Remove a reaction from an issue or pull request comment.", + "inputSchema": { + "properties": { + "comment_id": { + "description": "The issue or pull request comment ID", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "reaction_id": { + "description": "The reaction ID to remove", + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id", + "reaction_id" + ], + "type": "object" + }, + "name": "remove_issue_comment_reaction" +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_issue_reaction.snap new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_issue_reaction.snap --- old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_issue_reaction.snap 1970-01-01 01:00:00.000000000 +0100 +++ new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_issue_reaction.snap 2026-09-16 11:58:29.000000000 +0200 @@ -0,0 +1,40 @@ +{ + "annotations": { + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Remove Reaction from Issue or Pull Request" + }, + "description": "Remove a reaction from an issue or pull request.", + "inputSchema": { + "properties": { + "issue_number": { + "description": "The issue number", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "reaction_id": { + "description": "The reaction ID to remove", + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "issue_number", + "reaction_id" + ], + "type": "object" + }, + "name": "remove_issue_reaction" +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_pull_request_review_comment_reaction.snap new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_pull_request_review_comment_reaction.snap --- old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/remove_pull_request_review_comment_reaction.snap 1970-01-01 01:00:00.000000000 +0100 +++ new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/remove_pull_request_review_comment_reaction.snap 2026-09-16 11:58:29.000000000 +0200 @@ -0,0 +1,40 @@ +{ + "annotations": { + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Remove Pull Request Review Comment Reaction" + }, + "description": "Remove a reaction from a pull request review comment.", + "inputSchema": { + "properties": { + "comment_id": { + "description": "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "reaction_id": { + "description": "The reaction ID to remove", + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id", + "reaction_id" + ], + "type": "object" + }, + "name": "remove_pull_request_review_comment_reaction" +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/update_issue_comment.snap new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/update_issue_comment.snap --- old/github-mcp-server-1.12.1/pkg/github/__toolsnaps__/update_issue_comment.snap 1970-01-01 01:00:00.000000000 +0100 +++ new/github-mcp-server-1.12.2/pkg/github/__toolsnaps__/update_issue_comment.snap 2026-09-16 11:58:29.000000000 +0200 @@ -0,0 +1,38 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": false, + "title": "Update issue comment" + }, + "description": "Update the body of an existing issue or pull request conversation comment. This tool cannot update pull request review comments.", + "inputSchema": { + "properties": { + "body": { + "description": "New comment content", + "minLength": 1, + "type": "string" + }, + "comment_id": { + "description": "The numeric ID of the issue or pull request conversation comment to update. Do not use a pull request review comment ID.", + "minimum": 1, + "type": "integer" + }, + "owner": { + "description": "Repository owner", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id", + "body" + ], + "type": "object" + }, + "name": "update_issue_comment" +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/granular_tools_test.go new/github-mcp-server-1.12.2/pkg/github/granular_tools_test.go --- old/github-mcp-server-1.12.1/pkg/github/granular_tools_test.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/granular_tools_test.go 2026-09-16 11:58:29.000000000 +0200 @@ -53,7 +53,9 @@ GranularReprioritizeSubIssue, GranularSetIssueFields, GranularAddIssueReaction, + GranularRemoveIssueReaction, GranularAddIssueCommentReaction, + GranularRemoveIssueCommentReaction, GranularUpdatePullRequestTitle, GranularUpdatePullRequestBody, GranularUpdatePullRequestState, @@ -66,6 +68,7 @@ GranularResolveReviewThread, GranularUnresolveReviewThread, GranularAddPullRequestReviewCommentReaction, + GranularRemovePullRequestReviewCommentReaction, } for _, constructor := range toolConstructors { @@ -99,7 +102,9 @@ "reprioritize_sub_issue", "set_issue_fields", "add_issue_reaction", + "remove_issue_reaction", "add_issue_comment_reaction", + "remove_issue_comment_reaction", } for _, name := range expected { assert.Contains(t, toolNames, name) @@ -136,6 +141,7 @@ "resolve_review_thread", "unresolve_review_thread", "add_pull_request_review_comment_reaction", + "remove_pull_request_review_comment_reaction", } for _, name := range expected { assert.Contains(t, toolNames, name) @@ -2472,6 +2478,72 @@ } } +func TestGranularRemoveIssueReaction(t *testing.T) { + tests := []struct { + name string + mockedClient *http.Client + args map[string]any + expectedErrMsg string + }{ + { + name: "remove reaction from issue successfully", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesReactionsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNoContent, nil), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + "reaction_id": float64(12345), + }, + }, + { + name: "missing reaction_id returns error", + mockedClient: MockHTTPClientWithHandlers(nil), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + }, + expectedErrMsg: "missing required parameter: reaction_id", + }, + { + name: "API error", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesReactionsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message":"Not Found"}`), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + "reaction_id": float64(12345), + }, + expectedErrMsg: "failed to remove reaction from issue", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := mustNewGHClient(t, tc.mockedClient) + deps := BaseDeps{Client: client} + serverTool := GranularRemoveIssueReaction(translations.NullTranslationHelper) + require.NotNil(t, serverTool.Tool.Annotations.DestructiveHint) + assert.True(t, *serverTool.Tool.Annotations.DestructiveHint) + handler := serverTool.Handler(deps) + request := createMCPRequest(tc.args) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + if tc.expectedErrMsg != "" { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.expectedErrMsg) + return + } + require.False(t, result.IsError) + assert.Equal(t, "reaction successfully removed from issue", getTextResult(t, result).Text) + }) + } +} + func TestGranularAddIssueCommentReaction(t *testing.T) { mockReaction := &gogithub.Reaction{ ID: gogithub.Ptr(int64(67890)), @@ -2532,6 +2604,72 @@ } } +func TestGranularRemoveIssueCommentReaction(t *testing.T) { + tests := []struct { + name string + mockedClient *http.Client + args map[string]any + expectedErrMsg string + }{ + { + name: "remove reaction from issue comment successfully", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusNoContent, nil), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(999), + "reaction_id": float64(67890), + }, + }, + { + name: "missing comment_id returns error", + mockedClient: MockHTTPClientWithHandlers(nil), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "reaction_id": float64(67890), + }, + expectedErrMsg: "missing required parameter: comment_id", + }, + { + name: "API error", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusNotFound, `{"message":"Not Found"}`), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(999), + "reaction_id": float64(67890), + }, + expectedErrMsg: "failed to remove reaction from issue comment", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := mustNewGHClient(t, tc.mockedClient) + deps := BaseDeps{Client: client} + serverTool := GranularRemoveIssueCommentReaction(translations.NullTranslationHelper) + require.NotNil(t, serverTool.Tool.Annotations.DestructiveHint) + assert.True(t, *serverTool.Tool.Annotations.DestructiveHint) + handler := serverTool.Handler(deps) + request := createMCPRequest(tc.args) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + if tc.expectedErrMsg != "" { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.expectedErrMsg) + return + } + require.False(t, result.IsError) + assert.Equal(t, "reaction successfully removed from issue comment", getTextResult(t, result).Text) + }) + } +} + func TestGranularAddPullRequestReviewCommentReaction(t *testing.T) { mockReaction := &gogithub.Reaction{ ID: gogithub.Ptr(int64(54321)), @@ -2591,3 +2729,69 @@ }) } } + +func TestGranularRemovePullRequestReviewCommentReaction(t *testing.T) { + tests := []struct { + name string + mockedClient *http.Client + args map[string]any + expectedErrMsg string + }{ + { + name: "remove reaction from PR review comment successfully", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposPullsCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusNoContent, nil), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(888), + "reaction_id": float64(54321), + }, + }, + { + name: "missing repo returns error", + mockedClient: MockHTTPClientWithHandlers(nil), + args: map[string]any{ + "owner": "owner", + "comment_id": float64(888), + "reaction_id": float64(54321), + }, + expectedErrMsg: "missing required parameter: repo", + }, + { + name: "API error", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposPullsCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusNotFound, `{"message":"Not Found"}`), + }), + args: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(888), + "reaction_id": float64(54321), + }, + expectedErrMsg: "failed to remove reaction from pull request review comment", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := mustNewGHClient(t, tc.mockedClient) + deps := BaseDeps{Client: client} + serverTool := GranularRemovePullRequestReviewCommentReaction(translations.NullTranslationHelper) + require.NotNil(t, serverTool.Tool.Annotations.DestructiveHint) + assert.True(t, *serverTool.Tool.Annotations.DestructiveHint) + handler := serverTool.Handler(deps) + request := createMCPRequest(tc.args) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + if tc.expectedErrMsg != "" { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.expectedErrMsg) + return + } + require.False(t, result.IsError) + assert.Equal(t, "reaction successfully removed from pull request review comment", getTextResult(t, result).Text) + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/helper_test.go new/github-mcp-server-1.12.2/pkg/github/helper_test.go --- old/github-mcp-server-1.12.1/pkg/github/helper_test.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/helper_test.go 2026-09-16 11:58:29.000000000 +0200 @@ -64,13 +64,16 @@ GetReposIssuesCommentsByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/comments" PostReposIssuesByOwnerByRepo = "POST /repos/{owner}/{repo}/issues" PostReposIssuesCommentsByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/comments" + PatchReposIssuesCommentByOwnerByRepoByCommentID = "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}" PostReposIssuesReactionsByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/reactions" + DeleteReposIssuesReactionsByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}" PatchReposIssuesByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}" GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue" PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority" PostReposIssuesCommentsReactionsByOwnerByRepoByCommentID = "POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions" + DeleteReposIssuesCommentsReactionsByOwnerByRepoByCommentID = "DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}" DeleteReposIssuesIssueFieldValueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/issue-field-values/{issue_field_id}" // Pull request endpoints @@ -86,6 +89,7 @@ PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber = "POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers" PostReposPullsCommentsByOwnerByRepoByPullNumber = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments" PostReposPullsCommentsReactionsByOwnerByRepoByCommentID = "POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions" + DeleteReposPullsCommentsReactionsByOwnerByRepoByCommentID = "DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}" // Notifications endpoints GetNotifications = "GET /notifications" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/issues.go new/github-mcp-server-1.12.2/pkg/github/issues.go --- old/github-mcp-server-1.12.1/pkg/github/issues.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/issues.go 2026-09-16 11:58:29.000000000 +0200 @@ -1552,6 +1552,97 @@ }) } +// UpdateIssueComment creates a tool to update an issue or pull request conversation comment. +func UpdateIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool { + return NewTool( + ToolsetMetadataIssues, + mcp.Tool{ + Name: "update_issue_comment", + Description: t("TOOL_UPDATE_ISSUE_COMMENT_DESCRIPTION", "Update the body of an existing issue or pull request conversation comment. This tool cannot update pull request review comments."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_UPDATE_ISSUE_COMMENT_USER_TITLE", "Update issue comment"), + ReadOnlyHint: false, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "owner": { + Type: "string", + Description: "Repository owner", + }, + "repo": { + Type: "string", + Description: "Repository name", + }, + "comment_id": { + Type: "integer", + Description: "The numeric ID of the issue or pull request conversation comment to update. Do not use a pull request review comment ID.", + Minimum: jsonschema.Ptr(1.0), + }, + "body": { + Type: "string", + Description: "New comment content", + MinLength: jsonschema.Ptr(1), + }, + }, + Required: []string{"owner", "repo", "comment_id", "body"}, + }, + }, + publicRepositoryWriteScopeAccess(), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + repo, err := RequiredParam[string](args, "repo") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + commentID, err := RequiredBigInt(args, "comment_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + if commentID < 1 { + return utils.NewToolResultError("comment_id must be greater than 0"), nil, nil + } + body, hasBody, err := OptionalParamOK[string](args, "body") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + if !hasBody { + return utils.NewToolResultError("missing required parameter: body"), nil, nil + } + if body == "" { + return utils.NewToolResultError("body cannot be empty when provided"), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil + } + + updatedComment, resp, err := client.Issues.EditComment(ctx, owner, repo, commentID, &github.IssueComment{ + Body: github.Ptr(body), + }) + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to update issue comment", resp, err), nil, nil + } + + r, err := json.Marshal(MinimalResponse{ + ID: fmt.Sprintf("%d", updatedComment.GetID()), + URL: updatedComment.GetHTMLURL(), + }) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil + } + + return utils.NewToolResultText(string(r)), nil, nil + }) +} + func isValidIssueReaction(reaction string) bool { switch reaction { case "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/issues_granular.go new/github-mcp-server-1.12.2/pkg/github/issues_granular.go --- old/github-mcp-server-1.12.1/pkg/github/issues_granular.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/issues_granular.go 2026-09-16 11:58:29.000000000 +0200 @@ -1666,6 +1666,83 @@ return st } +// GranularRemoveIssueReaction removes a reaction from an issue or pull request. +func GranularRemoveIssueReaction(t translations.TranslationHelperFunc) inventory.ServerTool { + st := NewTool( + ToolsetMetadataIssues, + mcp.Tool{ + Name: "remove_issue_reaction", + Description: t("TOOL_REMOVE_ISSUE_REACTION_DESCRIPTION", "Remove a reaction from an issue or pull request."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_REMOVE_ISSUE_REACTION_USER_TITLE", "Remove Reaction from Issue or Pull Request"), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(true), + OpenWorldHint: jsonschema.Ptr(true), + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "owner": { + Type: "string", + Description: "Repository owner (username or organization)", + }, + "repo": { + Type: "string", + Description: "Repository name", + }, + "issue_number": { + Type: "number", + Description: "The issue number", + Minimum: jsonschema.Ptr(1.0), + }, + "reaction_id": { + Type: "number", + Description: "The reaction ID to remove", + Minimum: jsonschema.Ptr(1.0), + }, + }, + Required: []string{"owner", "repo", "issue_number", "reaction_id"}, + }, + }, + scopes.RequireAll(scopes.Repo), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + repo, err := RequiredParam[string](args, "repo") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + issueNumber, err := RequiredInt(args, "issue_number") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + reactionID, err := RequiredBigInt(args, "reaction_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil + } + + resp, err := client.Reactions.DeleteIssueReaction(ctx, owner, repo, issueNumber, reactionID) + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to remove reaction from issue", resp, err), nil, nil + } + + return utils.NewToolResultText("reaction successfully removed from issue"), nil, nil + }, + ) + st.FeatureRule = issuesGranularFeatureRule + return st +} + // GranularAddIssueCommentReaction adds a reaction to an issue or pull request comment. func GranularAddIssueCommentReaction(t translations.TranslationHelperFunc) inventory.ServerTool { st := NewTool( @@ -1745,5 +1822,82 @@ }, ) st.FeatureRule = issuesGranularFeatureRule + return st +} + +// GranularRemoveIssueCommentReaction removes a reaction from an issue or pull request comment. +func GranularRemoveIssueCommentReaction(t translations.TranslationHelperFunc) inventory.ServerTool { + st := NewTool( + ToolsetMetadataIssues, + mcp.Tool{ + Name: "remove_issue_comment_reaction", + Description: t("TOOL_REMOVE_ISSUE_COMMENT_REACTION_DESCRIPTION", "Remove a reaction from an issue or pull request comment."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_REMOVE_ISSUE_COMMENT_REACTION_USER_TITLE", "Remove Reaction from Issue or Pull Request Comment"), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(true), + OpenWorldHint: jsonschema.Ptr(true), + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "owner": { + Type: "string", + Description: "Repository owner (username or organization)", + }, + "repo": { + Type: "string", + Description: "Repository name", + }, + "comment_id": { + Type: "number", + Description: "The issue or pull request comment ID", + Minimum: jsonschema.Ptr(1.0), + }, + "reaction_id": { + Type: "number", + Description: "The reaction ID to remove", + Minimum: jsonschema.Ptr(1.0), + }, + }, + Required: []string{"owner", "repo", "comment_id", "reaction_id"}, + }, + }, + scopes.RequireAll(scopes.Repo), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + repo, err := RequiredParam[string](args, "repo") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + commentID, err := RequiredBigInt(args, "comment_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + reactionID, err := RequiredBigInt(args, "reaction_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil + } + + resp, err := client.Reactions.DeleteIssueCommentReaction(ctx, owner, repo, commentID, reactionID) + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to remove reaction from issue comment", resp, err), nil, nil + } + + return utils.NewToolResultText("reaction successfully removed from issue comment"), nil, nil + }, + ) + st.FeatureRule = issuesGranularFeatureRule return st } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/issues_test.go new/github-mcp-server-1.12.2/pkg/github/issues_test.go --- old/github-mcp-server-1.12.1/pkg/github/issues_test.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/issues_test.go 2026-09-16 11:58:29.000000000 +0200 @@ -6639,6 +6639,195 @@ } } +func TestUpdateIssueCommentSchema(t *testing.T) { + t.Parallel() + + tool := UpdateIssueComment(translations.NullTranslationHelper).Tool + require.NoError(t, toolsnaps.Test(tool.Name, tool)) + + assert.Equal(t, "update_issue_comment", tool.Name) + assert.NotEmpty(t, tool.Description) + schema := tool.InputSchema.(*jsonschema.Schema) + assert.Contains(t, schema.Properties, "owner") + assert.Contains(t, schema.Properties, "repo") + assert.Contains(t, schema.Properties, "comment_id") + assert.Contains(t, schema.Properties, "body") + assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "comment_id", "body"}) + + resolved, err := schema.Resolve(nil) + require.NoError(t, err) + + baseArgs := map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": 456, + "body": "Updated comment", + } + tests := []struct { + name string + args map[string]any + isValid bool + }{ + { + name: "valid arguments", + args: map[string]any{}, + isValid: true, + }, + { + name: "missing required body", + args: map[string]any{"body": nil}, + isValid: false, + }, + { + name: "empty body", + args: map[string]any{"body": ""}, + isValid: false, + }, + { + name: "zero comment ID", + args: map[string]any{"comment_id": 0}, + isValid: false, + }, + { + name: "fractional comment ID", + args: map[string]any{"comment_id": 1.5}, + isValid: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + args := maps.Clone(baseArgs) + maps.Copy(args, tc.args) + err := resolved.Validate(args) + if tc.isValid { + require.NoError(t, err) + return + } + require.Error(t, err) + }) + } +} + +func TestUpdateIssueCommentHandler(t *testing.T) { + t.Parallel() + + updatedComment := &github.IssueComment{ + ID: github.Ptr(int64(456)), + Body: github.Ptr("Updated comment"), + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-456"), + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]any + expectToolError bool + expectedToolErrMsg string + }{ + { + name: "successful update", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesCommentByOwnerByRepoByCommentID: expectRequestBody(t, map[string]any{ + "body": "Updated comment", + }).andThen(mockResponse(t, http.StatusOK, updatedComment)), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(456), + "body": "Updated comment", + }, + }, + { + name: "missing body", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(456), + }, + expectToolError: true, + expectedToolErrMsg: "missing required parameter: body", + }, + { + name: "empty body", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(456), + "body": "", + }, + expectToolError: true, + expectedToolErrMsg: "body cannot be empty when provided", + }, + { + name: "negative comment ID", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(-1), + "body": "Updated comment", + }, + expectToolError: true, + expectedToolErrMsg: "comment_id must be greater than 0", + }, + { + name: "fractional comment ID", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(1.5), + "body": "Updated comment", + }, + expectToolError: true, + expectedToolErrMsg: "parameter comment_id is not a valid number", + }, + { + name: "API error", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesCommentByOwnerByRepoByCommentID: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(456), + "body": "Updated comment", + }, + expectToolError: true, + expectedToolErrMsg: "failed to update issue comment", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client := mustNewGHClient(t, tc.mockedClient) + deps := BaseDeps{Client: client} + serverTool := UpdateIssueComment(translations.NullTranslationHelper) + handler := serverTool.Handler(deps) + + request := createMCPRequest(tc.requestArgs) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + + if tc.expectToolError { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.expectedToolErrMsg) + return + } + + require.False(t, result.IsError) + var response MinimalResponse + require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &response)) + assert.Equal(t, "456", response.ID) + assert.Equal(t, "https://github.com/owner/repo/issues/42#issuecomment-456", response.URL) + }) + } +} + func Test_RemoveSubIssue(t *testing.T) { // Verify tool definition once serverTool := SubIssueWrite(translations.NullTranslationHelper) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/public_repo_scopes_test.go new/github-mcp-server-1.12.2/pkg/github/public_repo_scopes_test.go --- old/github-mcp-server-1.12.1/pkg/github/public_repo_scopes_test.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/public_repo_scopes_test.go 2026-09-16 11:58:29.000000000 +0200 @@ -22,6 +22,7 @@ {name: "create_pull_request", tool: CreatePullRequest(translations.NullTranslationHelper)}, {name: "issue_write", tool: IssueWrite(translations.NullTranslationHelper)}, {name: "add_issue_comment", tool: AddIssueComment(translations.NullTranslationHelper)}, + {name: "update_issue_comment", tool: UpdateIssueComment(translations.NullTranslationHelper)}, } for _, tt := range tools { @@ -50,6 +51,7 @@ CreatePullRequest(translations.NullTranslationHelper), IssueWrite(translations.NullTranslationHelper), AddIssueComment(translations.NullTranslationHelper), + UpdateIssueComment(translations.NullTranslationHelper), } tests := []struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/pullrequests_granular.go new/github-mcp-server-1.12.2/pkg/github/pullrequests_granular.go --- old/github-mcp-server-1.12.1/pkg/github/pullrequests_granular.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/pullrequests_granular.go 2026-09-16 11:58:29.000000000 +0200 @@ -896,3 +896,80 @@ st.FeatureRule = pullRequestsGranularFeatureRule return st } + +// GranularRemovePullRequestReviewCommentReaction removes a reaction from a pull request review comment. +func GranularRemovePullRequestReviewCommentReaction(t translations.TranslationHelperFunc) inventory.ServerTool { + st := NewTool( + ToolsetMetadataPullRequests, + mcp.Tool{ + Name: "remove_pull_request_review_comment_reaction", + Description: t("TOOL_REMOVE_PULL_REQUEST_REVIEW_COMMENT_REACTION_DESCRIPTION", "Remove a reaction from a pull request review comment."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_REMOVE_PULL_REQUEST_REVIEW_COMMENT_REACTION_USER_TITLE", "Remove Pull Request Review Comment Reaction"), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(true), + OpenWorldHint: jsonschema.Ptr(true), + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "owner": { + Type: "string", + Description: "Repository owner (username or organization)", + }, + "repo": { + Type: "string", + Description: "Repository name", + }, + "comment_id": { + Type: "number", + Description: "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).", + Minimum: jsonschema.Ptr(1.0), + }, + "reaction_id": { + Type: "number", + Description: "The reaction ID to remove", + Minimum: jsonschema.Ptr(1.0), + }, + }, + Required: []string{"owner", "repo", "comment_id", "reaction_id"}, + }, + }, + scopes.RequireAll(scopes.Repo), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + repo, err := RequiredParam[string](args, "repo") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + commentID, err := RequiredBigInt(args, "comment_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + reactionID, err := RequiredBigInt(args, "reaction_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil + } + + resp, err := client.Reactions.DeletePullRequestCommentReaction(ctx, owner, repo, commentID, reactionID) + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to remove reaction from pull request review comment", resp, err), nil, nil + } + + return utils.NewToolResultText("reaction successfully removed from pull request review comment"), nil, nil + }, + ) + st.FeatureRule = pullRequestsGranularFeatureRule + return st +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/github-mcp-server-1.12.1/pkg/github/tools.go new/github-mcp-server-1.12.2/pkg/github/tools.go --- old/github-mcp-server-1.12.1/pkg/github/tools.go 2026-09-08 20:58:41.000000000 +0200 +++ new/github-mcp-server-1.12.2/pkg/github/tools.go 2026-09-16 11:58:29.000000000 +0200 @@ -259,6 +259,7 @@ ListIssueFields(t), IssueWrite(t), AddIssueComment(t), + UpdateIssueComment(t), SubIssueWrite(t), IssueDependencyRead(t), IssueDependencyWrite(t), @@ -372,7 +373,9 @@ GranularReprioritizeSubIssue(t), GranularSetIssueFields(t), GranularAddIssueReaction(t), + GranularRemoveIssueReaction(t), GranularAddIssueCommentReaction(t), + GranularRemoveIssueCommentReaction(t), // Granular pull request tools (feature-flagged, replace consolidated update_pull_request/pull_request_review_write) GranularUpdatePullRequestTitle(t), @@ -388,6 +391,7 @@ GranularResolveReviewThreadWithResolutionReason(t, opts...), GranularUnresolveReviewThread(t), GranularAddPullRequestReviewCommentReaction(t), + GranularRemovePullRequestReviewCommentReaction(t), }) } ++++++ vendor.tar.zst ++++++
