Skip to content

Commit f1813d7

Browse files
author
Matt Metzger
committed
Add support for retrieving GitHub Issue Comments
Add new tool 'get_issue_comments' that allows fetching comments associated with GitHub issues. This complements the existing issue retrieval functionality and follows the same patterns as the pull request comments implementation. The implementation includes: - New getIssueComments function in pkg/github/issues.go - Tool registration in server.go - Comprehensive test coverage in issues_test.go
1 parent 17e4bd4 commit f1813d7

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
154154
- `repo`: Repository name (string, required)
155155
- `issue_number`: Issue number (number, required)
156156

157+
- **get_issue_comments** - Get comments for a GitHub issue
158+
159+
- `owner`: Repository owner (string, required)
160+
- `repo`: Repository name (string, required)
161+
- `issue_number`: Issue number (number, required)
162+
157163
- **create_issue** - Create a new issue in a GitHub repository
158164

159165
- `owner`: Repository owner (string, required)

cmd/mcpcurl/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Available Commands:
5050
fork_repository Fork a GitHub repository to your account or specified organization
5151
get_file_contents Get the contents of a file or directory from a GitHub repository
5252
get_issue Get details of a specific issue in a GitHub repository.
53+
get_issue_comments Get comments for a GitHub issue
5354
list_commits Get list of commits of a branch in a GitHub repository
5455
list_issues List issues in a GitHub repository with filtering options
5556
push_files Push multiple files to a GitHub repository in a single commit

pkg/github/issues.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,66 @@ func updateIssue(client *github.Client, t translations.TranslationHelperFunc) (t
597597
}
598598
}
599599

600+
// getIssueComments creates a tool to get comments for a GitHub issue.
601+
func getIssueComments(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
602+
return mcp.NewTool("get_issue_comments",
603+
mcp.WithDescription(t("TOOL_GET_ISSUE_COMMENTS_DESCRIPTION", "Get comments for a GitHub issue")),
604+
mcp.WithString("owner",
605+
mcp.Required(),
606+
mcp.Description("Repository owner"),
607+
),
608+
mcp.WithString("repo",
609+
mcp.Required(),
610+
mcp.Description("Repository name"),
611+
),
612+
mcp.WithNumber("issue_number",
613+
mcp.Required(),
614+
mcp.Description("Issue number"),
615+
),
616+
),
617+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
618+
owner, err := requiredParam[string](request, "owner")
619+
if err != nil {
620+
return mcp.NewToolResultError(err.Error()), nil
621+
}
622+
repo, err := requiredParam[string](request, "repo")
623+
if err != nil {
624+
return mcp.NewToolResultError(err.Error()), nil
625+
}
626+
issueNumber, err := requiredInt(request, "issue_number")
627+
if err != nil {
628+
return mcp.NewToolResultError(err.Error()), nil
629+
}
630+
631+
opts := &github.IssueListCommentsOptions{
632+
ListOptions: github.ListOptions{
633+
PerPage: 100,
634+
},
635+
}
636+
637+
comments, resp, err := client.Issues.ListComments(ctx, owner, repo, issueNumber, opts)
638+
if err != nil {
639+
return nil, fmt.Errorf("failed to get issue comments: %w", err)
640+
}
641+
defer func() { _ = resp.Body.Close() }()
642+
643+
if resp.StatusCode != http.StatusOK {
644+
body, err := io.ReadAll(resp.Body)
645+
if err != nil {
646+
return nil, fmt.Errorf("failed to read response body: %w", err)
647+
}
648+
return mcp.NewToolResultError(fmt.Sprintf("failed to get issue comments: %s", string(body))), nil
649+
}
650+
651+
r, err := json.Marshal(comments)
652+
if err != nil {
653+
return nil, fmt.Errorf("failed to marshal response: %w", err)
654+
}
655+
656+
return mcp.NewToolResultText(string(r)), nil
657+
}
658+
}
659+
600660
// parseISOTimestamp parses an ISO 8601 timestamp string into a time.Time object.
601661
// Returns the parsed time or an error if parsing fails.
602662
// Example formats supported: "2023-01-15T14:30:00Z", "2023-01-15"

pkg/github/issues_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,112 @@ func Test_ParseISOTimestamp(t *testing.T) {
984984
})
985985
}
986986
}
987+
988+
func Test_GetIssueComments(t *testing.T) {
989+
// Verify tool definition once
990+
mockClient := github.NewClient(nil)
991+
tool, _ := getIssueComments(mockClient, translations.NullTranslationHelper)
992+
993+
assert.Equal(t, "get_issue_comments", tool.Name)
994+
assert.NotEmpty(t, tool.Description)
995+
assert.Contains(t, tool.InputSchema.Properties, "owner")
996+
assert.Contains(t, tool.InputSchema.Properties, "repo")
997+
assert.Contains(t, tool.InputSchema.Properties, "issue_number")
998+
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number"})
999+
1000+
// Setup mock comments for success case
1001+
mockComments := []*github.IssueComment{
1002+
{
1003+
ID: github.Ptr(int64(123)),
1004+
Body: github.Ptr("This is the first comment"),
1005+
User: &github.User{
1006+
Login: github.Ptr("user1"),
1007+
},
1008+
CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)},
1009+
},
1010+
{
1011+
ID: github.Ptr(int64(456)),
1012+
Body: github.Ptr("This is the second comment"),
1013+
User: &github.User{
1014+
Login: github.Ptr("user2"),
1015+
},
1016+
CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour)},
1017+
},
1018+
}
1019+
1020+
tests := []struct {
1021+
name string
1022+
mockedClient *http.Client
1023+
requestArgs map[string]interface{}
1024+
expectError bool
1025+
expectedComments []*github.IssueComment
1026+
expectedErrMsg string
1027+
}{
1028+
{
1029+
name: "successful comments retrieval",
1030+
mockedClient: mock.NewMockedHTTPClient(
1031+
mock.WithRequestMatch(
1032+
mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber,
1033+
mockComments,
1034+
),
1035+
),
1036+
requestArgs: map[string]interface{}{
1037+
"owner": "owner",
1038+
"repo": "repo",
1039+
"issue_number": float64(42),
1040+
},
1041+
expectError: false,
1042+
expectedComments: mockComments,
1043+
},
1044+
{
1045+
name: "issue not found",
1046+
mockedClient: mock.NewMockedHTTPClient(
1047+
mock.WithRequestMatchHandler(
1048+
mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber,
1049+
mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`),
1050+
),
1051+
),
1052+
requestArgs: map[string]interface{}{
1053+
"owner": "owner",
1054+
"repo": "repo",
1055+
"issue_number": float64(999),
1056+
},
1057+
expectError: true,
1058+
expectedErrMsg: "failed to get issue comments",
1059+
},
1060+
}
1061+
1062+
for _, tc := range tests {
1063+
t.Run(tc.name, func(t *testing.T) {
1064+
// Setup client with mock
1065+
client := github.NewClient(tc.mockedClient)
1066+
_, handler := getIssueComments(client, translations.NullTranslationHelper)
1067+
1068+
// Create call request
1069+
request := createMCPRequest(tc.requestArgs)
1070+
1071+
// Call handler
1072+
result, err := handler(context.Background(), request)
1073+
1074+
// Verify results
1075+
if tc.expectError {
1076+
require.Error(t, err)
1077+
assert.Contains(t, err.Error(), tc.expectedErrMsg)
1078+
return
1079+
}
1080+
1081+
require.NoError(t, err)
1082+
textContent := getTextResult(t, result)
1083+
1084+
// Unmarshal and verify the result
1085+
var returnedComments []*github.IssueComment
1086+
err = json.Unmarshal([]byte(textContent.Text), &returnedComments)
1087+
require.NoError(t, err)
1088+
assert.Equal(t, len(tc.expectedComments), len(returnedComments))
1089+
if len(returnedComments) > 0 {
1090+
assert.Equal(t, *tc.expectedComments[0].Body, *returnedComments[0].Body)
1091+
assert.Equal(t, *tc.expectedComments[0].User.Login, *returnedComments[0].User.Login)
1092+
}
1093+
})
1094+
}
1095+
}

pkg/github/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func NewServer(client *github.Client, readOnly bool, t translations.TranslationH
3434
s.AddTool(getIssue(client, t))
3535
s.AddTool(searchIssues(client, t))
3636
s.AddTool(listIssues(client, t))
37+
s.AddTool(getIssueComments(client, t))
3738
if !readOnly {
3839
s.AddTool(createIssue(client, t))
3940
s.AddTool(addIssueComment(client, t))

0 commit comments

Comments
 (0)