Skip to content

Commit 009bb7b

Browse files
Expose reactions through default comment tools
Keep the standalone reaction tools behind granular feature flags to avoid expanding the default tool count. Add optional reaction support to the existing issue comment and pull request comment reply tools, requiring at least one of body or reaction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 30da9da commit 009bb7b

11 files changed

Lines changed: 384 additions & 85 deletions

README.md

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -841,23 +841,10 @@ The following sets of tools are available:
841841

842842
- **add_issue_comment** - Add comment to issue or pull request
843843
- **Required OAuth Scopes**: `repo`
844-
- `body`: Comment content (string, required)
845-
- `issue_number`: Issue number to comment on (number, required)
844+
- `body`: Comment content. Required unless reaction is provided. (string, optional)
845+
- `issue_number`: Issue number to comment on or react to (number, required)
846846
- `owner`: Repository owner (string, required)
847-
- `repo`: Repository name (string, required)
848-
849-
- **add_issue_comment_reaction** - Add Reaction to Issue or Pull Request Comment
850-
- **Required OAuth Scopes**: `repo`
851-
- `comment_id`: The issue comment ID (number, required)
852-
- `content`: The emoji reaction type (string, required)
853-
- `owner`: Repository owner (username or organization) (string, required)
854-
- `repo`: Repository name (string, required)
855-
856-
- **add_issue_reaction** - Add Reaction to Issue or Pull Request
857-
- **Required OAuth Scopes**: `repo`
858-
- `content`: The emoji reaction type (string, required)
859-
- `issue_number`: The issue number (number, required)
860-
- `owner`: Repository owner (username or organization) (string, required)
847+
- `reaction`: Emoji reaction to add. Required unless body is provided. (string, optional)
861848
- `repo`: Repository name (string, required)
862849

863850
- **get_label** - Get a specific label from a repository
@@ -1109,19 +1096,13 @@ The following sets of tools are available:
11091096
- `startSide`: For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state (string, optional)
11101097
- `subjectType`: The level at which the comment is targeted (string, required)
11111098

1112-
- **add_pull_request_review_comment_reaction** - Add Pull Request Review Comment Reaction
1113-
- **Required OAuth Scopes**: `repo`
1114-
- `comment_id`: The pull request review comment ID (number, required)
1115-
- `content`: The emoji reaction type (string, required)
1116-
- `owner`: Repository owner (username or organization) (string, required)
1117-
- `repo`: Repository name (string, required)
1118-
11191099
- **add_reply_to_pull_request_comment** - Add reply to pull request comment
11201100
- **Required OAuth Scopes**: `repo`
1121-
- `body`: The text of the reply (string, required)
1122-
- `commentId`: The ID of the comment to reply to (number, required)
1101+
- `body`: The text of the reply. Required unless reaction is provided. (string, optional)
1102+
- `commentId`: The ID of the comment to reply or react to (number, required)
11231103
- `owner`: Repository owner (string, required)
1124-
- `pullNumber`: Pull request number (number, required)
1104+
- `pullNumber`: Pull request number. Required when body is provided. (number, optional)
1105+
- `reaction`: Emoji reaction to add. Required unless body is provided. (string, optional)
11251106
- `repo`: Repository name (string, required)
11261107

11271108
- **create_pull_request** - Open new pull request

docs/feature-flags.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ runtime behavior (such as output formatting) won't appear here.
9898

9999
### `issues_granular`
100100

101+
- **add_issue_comment_reaction** - Add Reaction to Issue or Pull Request Comment
102+
- **Required OAuth Scopes**: `repo`
103+
- `comment_id`: The issue comment ID (number, required)
104+
- `content`: The emoji reaction type (string, required)
105+
- `owner`: Repository owner (username or organization) (string, required)
106+
- `repo`: Repository name (string, required)
107+
108+
- **add_issue_reaction** - Add Reaction to Issue or Pull Request
109+
- **Required OAuth Scopes**: `repo`
110+
- `content`: The emoji reaction type (string, required)
111+
- `issue_number`: The issue number (number, required)
112+
- `owner`: Repository owner (username or organization) (string, required)
113+
- `repo`: Repository name (string, required)
114+
101115
- **add_sub_issue** - Add Sub-Issue
102116
- **Required OAuth Scopes**: `repo`
103117
- `issue_number`: The parent issue number (number, required)
@@ -204,6 +218,13 @@ runtime behavior (such as output formatting) won't appear here.
204218
- `startSide`: The start side of a multi-line comment (optional) (string, optional)
205219
- `subjectType`: The subject type of the comment (string, required)
206220

221+
- **add_pull_request_review_comment_reaction** - Add Pull Request Review Comment Reaction
222+
- **Required OAuth Scopes**: `repo`
223+
- `comment_id`: The pull request review comment ID (number, required)
224+
- `content`: The emoji reaction type (string, required)
225+
- `owner`: Repository owner (username or organization) (string, required)
226+
- `repo`: Repository name (string, required)
227+
207228
- **create_pull_request_review** - Create Pull Request Review
208229
- **Required OAuth Scopes**: `repo`
209230
- `body`: The review body text (optional) (string, optional)

pkg/github/__toolsnaps__/add_issue_comment.snap

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,35 @@
22
"annotations": {
33
"title": "Add comment to issue or pull request"
44
},
5-
"description": "Add a comment to a specific issue in a GitHub repository. Use this tool to add comments to pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add review comments.",
5+
"description": "Add a comment and/or reaction to a specific issue in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required.",
66
"inputSchema": {
77
"properties": {
88
"body": {
9-
"description": "Comment content",
9+
"description": "Comment content. Required unless reaction is provided.",
1010
"type": "string"
1111
},
1212
"issue_number": {
13-
"description": "Issue number to comment on",
13+
"description": "Issue number to comment on or react to",
1414
"type": "number"
1515
},
1616
"owner": {
1717
"description": "Repository owner",
1818
"type": "string"
1919
},
20+
"reaction": {
21+
"description": "Emoji reaction to add. Required unless body is provided.",
22+
"enum": [
23+
"+1",
24+
"-1",
25+
"laugh",
26+
"confused",
27+
"heart",
28+
"hooray",
29+
"rocket",
30+
"eyes"
31+
],
32+
"type": "string"
33+
},
2034
"repo": {
2135
"description": "Repository name",
2236
"type": "string"
@@ -25,8 +39,7 @@
2539
"required": [
2640
"owner",
2741
"repo",
28-
"issue_number",
29-
"body"
42+
"issue_number"
3043
],
3144
"type": "object"
3245
},

pkg/github/__toolsnaps__/add_reply_to_pull_request_comment.snap

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,39 @@
22
"annotations": {
33
"title": "Add reply to pull request comment"
44
},
5-
"description": "Add a reply to an existing pull request comment. This creates a new comment that is linked as a reply to the specified comment.",
5+
"description": "Add a reply and/or reaction to an existing pull request comment. This can create a new comment linked as a reply to the specified comment, add an emoji reaction to the specified comment, or do both. At least one of body or reaction is required.",
66
"inputSchema": {
77
"properties": {
88
"body": {
9-
"description": "The text of the reply",
9+
"description": "The text of the reply. Required unless reaction is provided.",
1010
"type": "string"
1111
},
1212
"commentId": {
13-
"description": "The ID of the comment to reply to",
13+
"description": "The ID of the comment to reply or react to",
1414
"type": "number"
1515
},
1616
"owner": {
1717
"description": "Repository owner",
1818
"type": "string"
1919
},
2020
"pullNumber": {
21-
"description": "Pull request number",
21+
"description": "Pull request number. Required when body is provided.",
2222
"type": "number"
2323
},
24+
"reaction": {
25+
"description": "Emoji reaction to add. Required unless body is provided.",
26+
"enum": [
27+
"+1",
28+
"-1",
29+
"laugh",
30+
"confused",
31+
"heart",
32+
"hooray",
33+
"rocket",
34+
"eyes"
35+
],
36+
"type": "string"
37+
},
2438
"repo": {
2539
"description": "Repository name",
2640
"type": "string"
@@ -29,9 +43,7 @@
2943
"required": [
3044
"owner",
3145
"repo",
32-
"pullNumber",
33-
"commentId",
34-
"body"
46+
"commentId"
3547
],
3648
"type": "object"
3749
},

pkg/github/granular_tools_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ func TestIssuesGranularToolset(t *testing.T) {
9090
"remove_sub_issue",
9191
"reprioritize_sub_issue",
9292
"set_issue_fields",
93+
"add_issue_reaction",
94+
"add_issue_comment_reaction",
9395
}
9496
for _, name := range expected {
9597
assert.Contains(t, toolNames, name)
@@ -125,6 +127,7 @@ func TestPullRequestsGranularToolset(t *testing.T) {
125127
"add_pull_request_review_comment",
126128
"resolve_review_thread",
127129
"unresolve_review_thread",
130+
"add_pull_request_review_comment_reaction",
128131
}
129132
for _, name := range expected {
130133
assert.Contains(t, toolNames, name)

pkg/github/issues.go

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,13 +1051,13 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool {
10511051
})
10521052
}
10531053

1054-
// AddIssueComment creates a tool to add a comment to an issue.
1054+
// AddIssueComment creates a tool to add a comment or reaction to an issue.
10551055
func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool {
10561056
return NewTool(
10571057
ToolsetMetadataIssues,
10581058
mcp.Tool{
10591059
Name: "add_issue_comment",
1060-
Description: t("TOOL_ADD_ISSUE_COMMENT_DESCRIPTION", "Add a comment to a specific issue in a GitHub repository. Use this tool to add comments to pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add review comments."),
1060+
Description: t("TOOL_ADD_ISSUE_COMMENT_DESCRIPTION", "Add a comment and/or reaction to a specific issue in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required."),
10611061
Annotations: &mcp.ToolAnnotations{
10621062
Title: t("TOOL_ADD_ISSUE_COMMENT_USER_TITLE", "Add comment to issue or pull request"),
10631063
ReadOnlyHint: false,
@@ -1075,14 +1075,19 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool
10751075
},
10761076
"issue_number": {
10771077
Type: "number",
1078-
Description: "Issue number to comment on",
1078+
Description: "Issue number to comment on or react to",
10791079
},
10801080
"body": {
10811081
Type: "string",
1082-
Description: "Comment content",
1082+
Description: "Comment content. Required unless reaction is provided.",
1083+
},
1084+
"reaction": {
1085+
Type: "string",
1086+
Description: "Emoji reaction to add. Required unless body is provided.",
1087+
Enum: []any{"+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes"},
10831088
},
10841089
},
1085-
Required: []string{"owner", "repo", "issue_number", "body"},
1090+
Required: []string{"owner", "repo", "issue_number"},
10861091
},
10871092
},
10881093
[]scopes.Scope{scopes.Repo},
@@ -1099,39 +1104,82 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool
10991104
if err != nil {
11001105
return utils.NewToolResultError(err.Error()), nil, nil
11011106
}
1102-
body, err := RequiredParam[string](args, "body")
1107+
body, hasBody, err := OptionalParamOK[string](args, "body")
11031108
if err != nil {
11041109
return utils.NewToolResultError(err.Error()), nil, nil
11051110
}
1106-
1107-
comment := &github.IssueComment{
1108-
Body: github.Ptr(body),
1111+
reactionContent, hasReaction, err := OptionalParamOK[string](args, "reaction")
1112+
if err != nil {
1113+
return utils.NewToolResultError(err.Error()), nil, nil
1114+
}
1115+
if !hasBody && !hasReaction {
1116+
return utils.NewToolResultError("at least one of body or reaction is required"), nil, nil
1117+
}
1118+
if hasBody && body == "" {
1119+
return utils.NewToolResultError("body cannot be empty when provided"), nil, nil
1120+
}
1121+
if hasReaction && reactionContent == "" {
1122+
return utils.NewToolResultError("reaction cannot be empty when provided"), nil, nil
11091123
}
11101124

11111125
client, err := deps.GetClient(ctx)
11121126
if err != nil {
11131127
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
11141128
}
1115-
createdComment, resp, err := client.Issues.CreateComment(ctx, owner, repo, issueNumber, comment)
1116-
if err != nil {
1117-
return utils.NewToolResultErrorFromErr("failed to create comment", err), nil, nil
1129+
1130+
var commentResponse *MinimalResponse
1131+
if hasBody {
1132+
comment := &github.IssueComment{
1133+
Body: github.Ptr(body),
1134+
}
1135+
createdComment, resp, err := client.Issues.CreateComment(ctx, owner, repo, issueNumber, comment)
1136+
if err != nil {
1137+
return utils.NewToolResultErrorFromErr("failed to create comment", err), nil, nil
1138+
}
1139+
defer func() { _ = resp.Body.Close() }()
1140+
1141+
if resp.StatusCode != http.StatusCreated {
1142+
bodyBytes, err := io.ReadAll(resp.Body)
1143+
if err != nil {
1144+
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
1145+
}
1146+
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to create comment", resp, bodyBytes), nil, nil
1147+
}
1148+
1149+
commentResponse = &MinimalResponse{
1150+
ID: fmt.Sprintf("%d", createdComment.GetID()),
1151+
URL: createdComment.GetHTMLURL(),
1152+
}
11181153
}
1119-
defer func() { _ = resp.Body.Close() }()
11201154

1121-
if resp.StatusCode != http.StatusCreated {
1122-
body, err := io.ReadAll(resp.Body)
1155+
var reactionResponse *MinimalResponse
1156+
if hasReaction {
1157+
reaction, resp, err := client.Reactions.CreateIssueReaction(ctx, owner, repo, issueNumber, reactionContent)
11231158
if err != nil {
1124-
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
1159+
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to add reaction to issue", resp, err), nil, nil
1160+
}
1161+
defer func() { _ = resp.Body.Close() }()
1162+
1163+
reactionResponse = &MinimalResponse{
1164+
ID: fmt.Sprintf("%d", reaction.GetID()),
1165+
URL: fmt.Sprintf("%srepos/%s/%s/issues/%d/reactions/%d", client.BaseURL(), owner, repo, issueNumber, reaction.GetID()),
11251166
}
1126-
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to create comment", resp, body), nil, nil
11271167
}
11281168

1129-
minimalResponse := MinimalResponse{
1130-
ID: fmt.Sprintf("%d", createdComment.GetID()),
1131-
URL: createdComment.GetHTMLURL(),
1169+
var result any
1170+
switch {
1171+
case hasBody && hasReaction:
1172+
result = map[string]MinimalResponse{
1173+
"comment": *commentResponse,
1174+
"reaction": *reactionResponse,
1175+
}
1176+
case hasReaction:
1177+
result = reactionResponse
1178+
default:
1179+
result = commentResponse
11321180
}
11331181

1134-
r, err := json.Marshal(minimalResponse)
1182+
r, err := json.Marshal(result)
11351183
if err != nil {
11361184
return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil
11371185
}

pkg/github/issues_granular.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,7 @@ func AddIssueReaction(t translations.TranslationHelperFunc) inventory.ServerTool
12771277
return utils.NewToolResultText(string(r)), nil, nil
12781278
},
12791279
)
1280+
st.FeatureFlagEnable = FeatureFlagIssuesGranular
12801281
return st
12811282
}
12821283

@@ -1358,5 +1359,6 @@ func AddIssueCommentReaction(t translations.TranslationHelperFunc) inventory.Ser
13581359
return utils.NewToolResultText(string(r)), nil, nil
13591360
},
13601361
)
1362+
st.FeatureFlagEnable = FeatureFlagIssuesGranular
13611363
return st
13621364
}

0 commit comments

Comments
 (0)