From a22f183043cffc3485f06d2c165ed2e677447caa Mon Sep 17 00:00:00 2001 From: Vidit-Ostwal Date: Sun, 14 Sep 2025 13:48:36 +0530 Subject: [PATCH 1/5] Added the update_issue_comment tool --- pkg/github/issues.go | 74 ++++++++++++++++++++++++++ pkg/github/issues_test.go | 106 ++++++++++++++++++++++++++++++++++++++ pkg/github/tools.go | 1 + 3 files changed, 181 insertions(+) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 1c88a9fde..f785bcebc 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -408,6 +408,80 @@ func AddIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc } } +// UpdateIssueComment creates a tool to update a previously added comment to an issue. +func UpdateIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("update_issue_comment", + mcp.WithDescription(t("TOOL_UPDATE_ISSUE_COMMENT_DESCRIPTION", "Update a previously added comment to a specific issue in a GitHub repository.")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_UPDATE_ISSUE_COMMENT_USER_TITLE", "Update a previously added comment to an issue"), + ReadOnlyHint: ToBoolPtr(false), + }), + mcp.WithNumber("comment_id", + mcp.Required(), + mcp.Description("ID of the comment to update"), + ), + mcp.WithString("owner", + mcp.Required(), + mcp.Description("Repository owner"), + ), + mcp.WithString("repo", + mcp.Required(), + mcp.Description("Repository name"), + ), + mcp.WithString("body", + mcp.Required(), + mcp.Description("Comment content"), + ), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + owner, err := RequiredParam[string](request, "owner") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + repo, err := RequiredParam[string](request, "repo") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + body, err := RequiredParam[string](request, "body") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + commentID, err := RequiredInt(request, "comment_id") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + comment := &github.IssueComment{ + Body: github.Ptr(body), + } + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + updatedComment, resp, err := client.Issues.EditComment(ctx, owner, repo, int64(commentID), comment) + if err != nil { + return nil, fmt.Errorf("failed to update comment: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to update comment: %s", string(body))), nil + } + + r, err := json.Marshal(updatedComment) + if err != nil { + return nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} + // AddSubIssue creates a tool to add a sub-issue to a parent issue. func AddSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("add_sub_issue", diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index cc1923df9..3d499168f 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -236,6 +236,112 @@ func Test_AddIssueComment(t *testing.T) { } } +func Test_UpdateIssueComment(t *testing.T) { + // Verify tool definition once + mockClient := github.NewClient(nil) + tool, _ := UpdateIssueComment(stubGetClientFn(mockClient), translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(tool.Name, tool)) + + assert.Equal(t, "update_issue_comment", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "owner") + assert.Contains(t, tool.InputSchema.Properties, "repo") + assert.Contains(t, tool.InputSchema.Properties, "comment_id") + assert.Contains(t, tool.InputSchema.Properties, "body") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "comment_id", "body"}) + + // Setup mock comment for success case + mockComment := &github.IssueComment{ + ID: github.Ptr(int64(123)), + Body: github.Ptr("This is a test comment"), + User: &github.User{ + Login: github.Ptr("testuser"), + }, + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-123"), + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedComment *github.IssueComment + expectedErrMsg string + }{ + { + name: "successful comment update", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PatchReposIssuesCommentsByOwnerByRepoByIssueNumber, + mockResponse(t, http.StatusOK, mockComment), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(123), + "body": "This is a test comment", + }, + expectError: false, + expectedComment: mockComment, + }, + { + name: "comment update fails", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PatchReposIssuesCommentsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "comment_id": float64(123), + "body": "", + }, + expectError: false, + expectedErrMsg: "missing required parameter: body", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := UpdateIssueComment(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + + // Unmarshal and verify the result + var returnedComment github.IssueComment + err = json.Unmarshal([]byte(textContent.Text), &returnedComment) + require.NoError(t, err) + assert.Equal(t, *tc.expectedComment.ID, *returnedComment.ID) + assert.Equal(t, *tc.expectedComment.Body, *returnedComment.Body) + assert.Equal(t, *tc.expectedComment.User.Login, *returnedComment.User.Login) + }) + } +} + func Test_SearchIssues(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 0f294cef6..2888fffe4 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -65,6 +65,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG AddWriteTools( toolsets.NewServerTool(CreateIssue(getClient, t)), toolsets.NewServerTool(AddIssueComment(getClient, t)), + toolsets.NewServerTool(UpdateIssueComment(getClient, t)), toolsets.NewServerTool(UpdateIssue(getClient, getGQLClient, t)), toolsets.NewServerTool(AssignCopilotToIssue(getGQLClient, t)), toolsets.NewServerTool(AddSubIssue(getClient, t)), From cef967c1b7dc5c1741365ffba8d2814d9e0c606e Mon Sep 17 00:00:00 2001 From: Vidit-Ostwal Date: Wed, 17 Sep 2025 16:52:23 +0530 Subject: [PATCH 2/5] Added the GetLatestIssueCommentUser tool --- pkg/github/issues.go | 99 ++++++++++++++++++++++++++++++++++++++++++++ pkg/github/tools.go | 1 + 2 files changed, 100 insertions(+) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index f785bcebc..ed0794700 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -1585,6 +1585,105 @@ func GetIssueComments(getClient GetClientFn, t translations.TranslationHelperFun } } +// GetIssueCommentUser creates a tool to get the latest comment by a user on a specific issue in a GitHub repository. +func GetLatestIssueCommentUser(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("get_latest_issue_comment_user", + mcp.WithDescription(t("TOOL_GET_LATEST_ISSUE_COMMENT_USER_DESCRIPTION", "Get the latest comments by a specific user on a specific issue in a GitHub repository.")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_GET_LATEST_ISSUE_COMMENT_USER_TITLE", "Get latest issue comment by user"), + ReadOnlyHint: ToBoolPtr(true), + }), + mcp.WithString("owner", + mcp.Required(), + mcp.Description("Repository owner"), + ), + mcp.WithString("repo", + mcp.Required(), + mcp.Description("Repository name"), + ), + mcp.WithNumber("issue_number", + mcp.Required(), + mcp.Description("Issue number"), + ), + mcp.WithString("user", + mcp.Required(), + mcp.Description("User"), + ), + WithPagination(), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + owner, err := RequiredParam[string](request, "owner") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + repo, err := RequiredParam[string](request, "repo") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + issueNumber, err := RequiredInt(request, "issue_number") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + user, err := RequiredParam[string](request, "user") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + pagination, err := OptionalPaginationParams(request) + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + opts := &github.IssueListCommentsOptions{ + ListOptions: github.ListOptions{ + Page: pagination.Page, + PerPage: pagination.PerPage, + }, + } + var latestcomment *github.IssueComment + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + comments, resp, err := client.Issues.ListComments(ctx, owner, repo, issueNumber, opts) + if err != nil { + return nil, fmt.Errorf("failed to get issue comments: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to get issue comments: %s", string(body))), nil + } + + for _, comment := range comments { + if comment == nil || comment.User == nil || comment.User.Login == nil { + continue + } + + if *comment.User.Login == user { + if latestcomment == nil { + latestcomment = comment + } else { + if comment.UpdatedAt.Time.After(latestcomment.UpdatedAt.Time) { + latestcomment = comment + } + } + } + } + + r, err := json.Marshal(latestcomment) + if err != nil { + return nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} + // mvpDescription is an MVP idea for generating tool descriptions from structured data in a shared format. // It is not intended for widespread usage and is not a complete implementation. type mvpDescription struct { diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 2888fffe4..8e46e794c 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -61,6 +61,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG toolsets.NewServerTool(GetIssueComments(getClient, t)), toolsets.NewServerTool(ListIssueTypes(getClient, t)), toolsets.NewServerTool(ListSubIssues(getClient, t)), + toolsets.NewServerTool(GetLatestIssueCommentUser(getClient, t)), ). AddWriteTools( toolsets.NewServerTool(CreateIssue(getClient, t)), From b6b7310a1b9a657dddae4944b66b69da00fd5805 Mon Sep 17 00:00:00 2001 From: Vidit-Ostwal Date: Wed, 17 Sep 2025 17:07:36 +0530 Subject: [PATCH 3/5] Merge Conflic --- pkg/github/issues_test.go | 106 -------------------------------------- 1 file changed, 106 deletions(-) diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 3d499168f..cc1923df9 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -236,112 +236,6 @@ func Test_AddIssueComment(t *testing.T) { } } -func Test_UpdateIssueComment(t *testing.T) { - // Verify tool definition once - mockClient := github.NewClient(nil) - tool, _ := UpdateIssueComment(stubGetClientFn(mockClient), translations.NullTranslationHelper) - require.NoError(t, toolsnaps.Test(tool.Name, tool)) - - assert.Equal(t, "update_issue_comment", tool.Name) - assert.NotEmpty(t, tool.Description) - assert.Contains(t, tool.InputSchema.Properties, "owner") - assert.Contains(t, tool.InputSchema.Properties, "repo") - assert.Contains(t, tool.InputSchema.Properties, "comment_id") - assert.Contains(t, tool.InputSchema.Properties, "body") - assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "comment_id", "body"}) - - // Setup mock comment for success case - mockComment := &github.IssueComment{ - ID: github.Ptr(int64(123)), - Body: github.Ptr("This is a test comment"), - User: &github.User{ - Login: github.Ptr("testuser"), - }, - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-123"), - } - - tests := []struct { - name string - mockedClient *http.Client - requestArgs map[string]interface{} - expectError bool - expectedComment *github.IssueComment - expectedErrMsg string - }{ - { - name: "successful comment update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesCommentsByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusOK, mockComment), - ), - ), - requestArgs: map[string]interface{}{ - "owner": "owner", - "repo": "repo", - "comment_id": float64(123), - "body": "This is a test comment", - }, - expectError: false, - expectedComment: mockComment, - }, - { - name: "comment update fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesCommentsByOwnerByRepoByIssueNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) - }), - ), - ), - requestArgs: map[string]interface{}{ - "owner": "owner", - "repo": "repo", - "comment_id": float64(123), - "body": "", - }, - expectError: false, - expectedErrMsg: "missing required parameter: body", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Setup client with mock - client := github.NewClient(tc.mockedClient) - _, handler := UpdateIssueComment(stubGetClientFn(client), translations.NullTranslationHelper) - - // Create call request - request := createMCPRequest(tc.requestArgs) - - // Call handler - result, err := handler(context.Background(), request) - - // Verify results - if tc.expectError { - require.Error(t, err) - assert.Contains(t, err.Error(), tc.expectedErrMsg) - return - } - - require.NoError(t, err) - - // Parse the result and get the text content if no error - textContent := getTextResult(t, result) - - // Unmarshal and verify the result - var returnedComment github.IssueComment - err = json.Unmarshal([]byte(textContent.Text), &returnedComment) - require.NoError(t, err) - assert.Equal(t, *tc.expectedComment.ID, *returnedComment.ID) - assert.Equal(t, *tc.expectedComment.Body, *returnedComment.Body) - assert.Equal(t, *tc.expectedComment.User.Login, *returnedComment.User.Login) - }) - } -} - func Test_SearchIssues(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) From 669c51befc478a4a254f79817d6826346f81f6c6 Mon Sep 17 00:00:00 2001 From: Vidit-Ostwal Date: Wed, 17 Sep 2025 18:01:10 +0530 Subject: [PATCH 4/5] added test cases --- .../get_latest_issue_comment_user.snap | 46 ++++++ pkg/github/issues_test.go | 147 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 pkg/github/__toolsnaps__/get_latest_issue_comment_user.snap diff --git a/pkg/github/__toolsnaps__/get_latest_issue_comment_user.snap b/pkg/github/__toolsnaps__/get_latest_issue_comment_user.snap new file mode 100644 index 000000000..617e4428b --- /dev/null +++ b/pkg/github/__toolsnaps__/get_latest_issue_comment_user.snap @@ -0,0 +1,46 @@ +{ + "annotations": { + "title": "Get latest issue comment by user", + "readOnlyHint": true + }, + "description": "Get the latest comments by a specific user on a specific issue in a GitHub repository.", + "inputSchema": { + "properties": { + "issue_number": { + "description": "Issue number", + "type": "number" + }, + "owner": { + "description": "Repository owner", + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + }, + "user": { + "description": "User", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "issue_number", + "user" + ], + "type": "object" + }, + "name": "get_latest_issue_comment_user" +} \ No newline at end of file diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index cc1923df9..37bb8efd8 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -3224,3 +3224,150 @@ func Test_ListIssueTypes(t *testing.T) { }) } } + +func Test_GetLatestIssueCommentUser(t *testing.T) { + // Verify tool definition once + mockClient := github.NewClient(nil) + tool, _ := GetLatestIssueCommentUser(stubGetClientFn(mockClient), translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(tool.Name, tool)) + + assert.Equal(t, "get_latest_issue_comment_user", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "owner") + assert.Contains(t, tool.InputSchema.Properties, "repo") + assert.Contains(t, tool.InputSchema.Properties, "issue_number") + assert.Contains(t, tool.InputSchema.Properties, "user") + assert.Contains(t, tool.InputSchema.Properties, "page") + assert.Contains(t, tool.InputSchema.Properties, "perPage") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number", "user"}) + + // Setup mock comments for success case + mockComments := []*github.IssueComment{{ + ID: github.Ptr(int64(123)), + Body: github.Ptr("This is the first comment"), + User: &github.User{ + Login: github.Ptr("user1"), + }, + CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)}, + UpdatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)}, + }, + { + ID: github.Ptr(int64(123)), + Body: github.Ptr("This is the first comment"), + User: &github.User{ + Login: github.Ptr("user2"), + }, + CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)}, + UpdatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)}, + }, + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedComments []*github.IssueComment + expectedErrMsg string + }{ + { + name: "successful comment retrieval", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, + mockComments, + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + "user": "user1", + }, + expectError: false, + expectedComments: mockComments, + }, + { + name: "successful comment retrieval with pagination", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, + expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockComments), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + "page": float64(2), + "perPage": float64(10), + "user": "user1", + }, + expectError: false, + expectedComments: mockComments, + }, + { + name: "comment retrieval fails", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(42), + "user": "", + }, + expectError: false, + expectedErrMsg: "missing required parameter: user", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := GetLatestIssueCommentUser(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + if tc.expectedErrMsg != "" { + require.NotNil(t, result) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, tc.expectedErrMsg) + return + } + + require.NoError(t, err) + textContent := getTextResult(t, result) + + // Unmarshal and verify the result + var returnedComments *github.IssueComment + err = json.Unmarshal([]byte(textContent.Text), &returnedComments) + require.NoError(t, err) + assert.Equal(t, *tc.expectedComments[0].Body, *returnedComments.Body) + assert.Equal(t, *tc.expectedComments[0].User.Login, *returnedComments.User.Login) + }) + } +} From 01e5d66974a958c8bde8556d8e62c88b0ee6054f Mon Sep 17 00:00:00 2001 From: Vidit-Ostwal Date: Wed, 17 Sep 2025 18:04:10 +0530 Subject: [PATCH 5/5] Updated README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 891e63a81..714415f71 100644 --- a/README.md +++ b/README.md @@ -602,6 +602,14 @@ The following sets of tools are available (all are on by default): - `title`: New title (string, optional) - `type`: New issue type (string, optional) +- **get_latest_issue_comment_user** - Get latest issue comments by a user on a particular issue + - `issue_number`: Issue number (number, required) + - `owner`: Repository owner (string, required) + - `page`: Page number for pagination (min 1) (number, optional) + - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) + - `repo`: Repository name (string, required) + - `user`: Github username of that particular user (string, required) +