From e733f06faeff8776819367215fa16a4f6988a58d Mon Sep 17 00:00:00 2001 From: Pranav RK Date: Thu, 5 Jun 2025 23:00:29 +0530 Subject: [PATCH 1/5] feat: add type to issues --- pkg/github/issues.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 7fba9f9d6..5324bc0ee 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -283,6 +283,9 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t mcp.WithNumber("milestone", mcp.Description("Milestone number"), ), + mcp.WithString("type", + mcp.Description("Type of this issue"), + ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") @@ -327,6 +330,12 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t milestoneNum = &milestone } + // Get optional type + issueType, err := OptionalParam[string](request, "type") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + // Create the issue request issueRequest := &github.IssueRequest{ Title: github.Ptr(title), @@ -334,6 +343,7 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t Assignees: &assignees, Labels: &labels, Milestone: milestoneNum, + Type: &issueType, } client, err := getClient(ctx) @@ -534,6 +544,9 @@ func UpdateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t mcp.WithNumber("milestone", mcp.Description("New milestone number"), ), + mcp.WithString("type", + mcp.Description("New issue type"), + ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") @@ -604,6 +617,15 @@ func UpdateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t issueRequest.Milestone = &milestoneNum } + // Get issue type + issueType, err := OptionalParam[string](request, "type") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + if issueType != "" { + issueRequest.Type = github.Ptr(issueType) + } + client, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) From 6a1695520587b351b5e82d28e8a63de4676155a7 Mon Sep 17 00:00:00 2001 From: Pranav RK Date: Sat, 7 Jun 2025 00:22:09 +0530 Subject: [PATCH 2/5] test: add `type` test for create and update issues --- pkg/github/issues_test.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 251fc32bf..164b32fa1 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -396,6 +396,7 @@ func Test_CreateIssue(t *testing.T) { assert.Contains(t, tool.InputSchema.Properties, "assignees") assert.Contains(t, tool.InputSchema.Properties, "labels") assert.Contains(t, tool.InputSchema.Properties, "milestone") + assert.Contains(t, tool.InputSchema.Properties, "type") assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "title"}) // Setup mock issue for success case @@ -408,6 +409,7 @@ func Test_CreateIssue(t *testing.T) { Assignees: []*github.User{{Login: github.Ptr("user1")}, {Login: github.Ptr("user2")}}, Labels: []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("help wanted")}}, Milestone: &github.Milestone{Number: github.Ptr(5)}, + Type: &github.IssueType{Name: github.Ptr("Bug")}, } tests := []struct { @@ -429,6 +431,7 @@ func Test_CreateIssue(t *testing.T) { "labels": []any{"bug", "help wanted"}, "assignees": []any{"user1", "user2"}, "milestone": float64(5), + "type": "Bug", }).andThen( mockResponse(t, http.StatusCreated, mockIssue), ), @@ -442,6 +445,7 @@ func Test_CreateIssue(t *testing.T) { "assignees": []any{"user1", "user2"}, "labels": []any{"bug", "help wanted"}, "milestone": float64(5), + "type": "Bug", }, expectError: false, expectedIssue: mockIssue, @@ -537,6 +541,10 @@ func Test_CreateIssue(t *testing.T) { assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body) } + if tc.expectedIssue.Type != nil { + assert.Equal(t, *tc.expectedIssue.Type.Name, *returnedIssue.Type.Name) + } + // Check assignees if expected if len(tc.expectedIssue.Assignees) > 0 { assert.Equal(t, len(tc.expectedIssue.Assignees), len(returnedIssue.Assignees)) @@ -748,6 +756,7 @@ func Test_UpdateIssue(t *testing.T) { assert.Contains(t, tool.InputSchema.Properties, "labels") assert.Contains(t, tool.InputSchema.Properties, "assignees") assert.Contains(t, tool.InputSchema.Properties, "milestone") + assert.Contains(t, tool.InputSchema.Properties, "type") assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number"}) // Setup mock issue for success case @@ -760,6 +769,7 @@ func Test_UpdateIssue(t *testing.T) { Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, Labels: []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}}, Milestone: &github.Milestone{Number: github.Ptr(5)}, + Type: &github.IssueType{Name: github.Ptr("Bug")}, } tests := []struct { @@ -782,6 +792,7 @@ func Test_UpdateIssue(t *testing.T) { "labels": []any{"bug", "priority"}, "assignees": []any{"assignee1", "assignee2"}, "milestone": float64(5), + "type": "Bug", }).andThen( mockResponse(t, http.StatusOK, mockIssue), ), @@ -797,6 +808,7 @@ func Test_UpdateIssue(t *testing.T) { "labels": []any{"bug", "priority"}, "assignees": []any{"assignee1", "assignee2"}, "milestone": float64(5), + "type": "Bug", }, expectError: false, expectedIssue: mockIssue, @@ -808,9 +820,10 @@ func Test_UpdateIssue(t *testing.T) { mock.PatchReposIssuesByOwnerByRepoByIssueNumber, mockResponse(t, http.StatusOK, &github.Issue{ Number: github.Ptr(123), - Title: github.Ptr("Only Title Updated"), + Title: github.Ptr("Updated Issue Title"), HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), State: github.Ptr("open"), + Type: &github.IssueType{Name: github.Ptr("Feature")}, }), ), ), @@ -818,14 +831,16 @@ func Test_UpdateIssue(t *testing.T) { "owner": "owner", "repo": "repo", "issue_number": float64(123), - "title": "Only Title Updated", + "title": "Updated Issue Title", + "type": "Feature", }, expectError: false, expectedIssue: &github.Issue{ Number: github.Ptr(123), - Title: github.Ptr("Only Title Updated"), + Title: github.Ptr("Updated Issue Title"), HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), State: github.Ptr("open"), + Type: &github.IssueType{Name: github.Ptr("Feature")}, }, }, { @@ -914,6 +929,10 @@ func Test_UpdateIssue(t *testing.T) { assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body) } + if tc.expectedIssue.Type != nil { + assert.Equal(t, *tc.expectedIssue.Type.Name, *returnedIssue.Type.Name) + } + // Check assignees if expected if len(tc.expectedIssue.Assignees) > 0 { assert.Len(t, returnedIssue.Assignees, len(tc.expectedIssue.Assignees)) From ec11defe1db42af997f4b2a25a70143d20f9bae0 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 13 Aug 2025 16:14:11 +0000 Subject: [PATCH 3/5] Generate docs and toolsnaps --- README.md | 2 ++ pkg/github/__toolsnaps__/create_issue.snap | 4 ++++ pkg/github/__toolsnaps__/update_issue.snap | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 68677e8b2..ae66df13a 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,7 @@ The following sets of tools are available (all are on by default): - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `title`: Issue title (string, required) + - `type`: Type of this issue (string, optional) - **get_issue** - Get issue details - `issue_number`: The number of the issue (number, required) @@ -592,6 +593,7 @@ The following sets of tools are available (all are on by default): - `repo`: Repository name (string, required) - `state`: New state (string, optional) - `title`: New title (string, optional) + - `type`: New issue type (string, optional) diff --git a/pkg/github/__toolsnaps__/create_issue.snap b/pkg/github/__toolsnaps__/create_issue.snap index f065b0183..d11c41c0e 100644 --- a/pkg/github/__toolsnaps__/create_issue.snap +++ b/pkg/github/__toolsnaps__/create_issue.snap @@ -39,6 +39,10 @@ "title": { "description": "Issue title", "type": "string" + }, + "type": { + "description": "Type of this issue", + "type": "string" } }, "required": [ diff --git a/pkg/github/__toolsnaps__/update_issue.snap b/pkg/github/__toolsnaps__/update_issue.snap index 4bcae7ba7..d95579159 100644 --- a/pkg/github/__toolsnaps__/update_issue.snap +++ b/pkg/github/__toolsnaps__/update_issue.snap @@ -51,6 +51,10 @@ "title": { "description": "New title", "type": "string" + }, + "type": { + "description": "New issue type", + "type": "string" } }, "required": [ From 64f6fb47dd6178faa947439541136700d25c469b Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 13 Aug 2025 18:17:41 +0200 Subject: [PATCH 4/5] Update pkg/github/issues.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/github/issues.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 2d07f1c37..d2a76f233 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -848,7 +848,7 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t Assignees: &assignees, Labels: &labels, Milestone: milestoneNum, - Type: &issueType, + Type: typePtr, } client, err := getClient(ctx) From 672873619fd0ef4ef5582b01d6b7e4e255523fc4 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 13 Aug 2025 16:20:29 +0000 Subject: [PATCH 5/5] Use github ptr --- pkg/github/issues.go | 71 +++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index d2a76f233..80fe22f9d 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -553,39 +553,39 @@ func RemoveSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc) } client, err := getClient(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get GitHub client: %w", err) - } - - subIssueRequest := github.SubIssueRequest{ - SubIssueID: int64(subIssueID), - } - - subIssue, resp, err := client.SubIssue.Remove(ctx, owner, repo, int64(issueNumber), subIssueRequest) - if err != nil { - return ghErrors.NewGitHubAPIErrorResponse(ctx, - "failed to remove sub-issue", - resp, - err, - ), nil - } - 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 remove sub-issue: %s", string(body))), nil - } - - r, err := json.Marshal(subIssue) - if err != nil { - return nil, fmt.Errorf("failed to marshal response: %w", err) - } - - return mcp.NewToolResultText(string(r)), nil - } + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + subIssueRequest := github.SubIssueRequest{ + SubIssueID: int64(subIssueID), + } + + subIssue, resp, err := client.SubIssue.Remove(ctx, owner, repo, int64(issueNumber), subIssueRequest) + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to remove sub-issue", + resp, + err, + ), nil + } + 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 remove sub-issue: %s", string(body))), nil + } + + r, err := json.Marshal(subIssue) + if err != nil { + return nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } } // ReprioritizeSubIssue creates a tool to reprioritize a sub-issue to a different position in the parent list. @@ -848,7 +848,10 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t Assignees: &assignees, Labels: &labels, Milestone: milestoneNum, - Type: typePtr, + } + + if issueType != "" { + issueRequest.Type = github.Ptr(issueType) } client, err := getClient(ctx)