From bfc1ff17daf73004c2f93f42380bb83f3c03f2e9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Aug 2026 23:27:34 -0700 Subject: [PATCH] feat(issue): add assigned_by filter to list_issues Add an optional assigned_by parameter to the list_issues tool that maps to gitea_sdk.ListIssueOption.AssignedBy, and include assignees in the slim list_issues output for consistency with the single-issue view. Co-Authored-By: Codet (GPT-5-Codex) --- operation/issue/issue.go | 4 +++ operation/issue/issue_test.go | 62 +++++++++++++++++++++++++++++++---- operation/issue/slim.go | 3 ++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/operation/issue/issue.go b/operation/issue/issue.go index e3703bc..83c892c 100644 --- a/operation/issue/issue.go +++ b/operation/issue/issue.go @@ -49,6 +49,7 @@ var ( tool.Array("milestones", tool.Description("milestone name or ID filter"), tool.Items(map[string]any{"type": "string"})), tool.String("since", tool.Description("updated after ISO 8601")), tool.String("before", tool.Description("updated before ISO 8601")), + tool.String("assigned_by", tool.Description("filter by the user who assigned the issue")), tool.Number("page", tool.Description(params.PageDesc), tool.Default(1)), tool.Number("per_page", tool.Description(params.PaginationDesc), tool.Default(30)), ) @@ -204,6 +205,9 @@ func listRepoIssuesFn(ctx context.Context, args map[string]any) (*mcp.CallToolRe if t := params.GetOptionalTime(args, "before"); t != nil { opt.Before = *t } + if assignedBy, ok := args["assigned_by"].(string); ok { + opt.AssignedBy = assignedBy + } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) diff --git a/operation/issue/issue_test.go b/operation/issue/issue_test.go index f65f0a9..d1c2628 100644 --- a/operation/issue/issue_test.go +++ b/operation/issue/issue_test.go @@ -61,12 +61,13 @@ func Test_listRepoIssuesFn_filters(t *testing.T) { }() args := map[string]any{ - "owner": owner, - "repo": repo, - "type": "issues", - "labels": []any{"bug", "enhancement"}, - "milestones": []any{"v1.0", "2"}, - "since": "2026-01-01T00:00:00Z", + "owner": owner, + "repo": repo, + "type": "issues", + "labels": []any{"bug", "enhancement"}, + "milestones": []any{"v1.0", "2"}, + "since": "2026-01-01T00:00:00Z", + "assigned_by": "octocat", } _, err := listRepoIssuesFn(context.Background(), args) @@ -89,6 +90,9 @@ func Test_listRepoIssuesFn_filters(t *testing.T) { if !strings.Contains(gotQuery, "type=issues") { t.Fatalf("expected type query param, got %s", gotQuery) } + if !strings.Contains(gotQuery, "assigned_by=octocat") { + t.Fatalf("expected assigned_by query param, got %s", gotQuery) + } } func Test_listRepoIssuesFn_includesMilestone(t *testing.T) { @@ -138,6 +142,52 @@ func Test_listRepoIssuesFn_includesMilestone(t *testing.T) { } } +func Test_listRepoIssuesFn_includesAssignees(t *testing.T) { + const ( + owner = "octo" + repo = "demo" + ) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/version": + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"version":"1.12.0"}`)) + case fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo): + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"private":false}`)) + case fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner, repo): + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[ + {"number": 1, "title": "with assignees", "state": "open", "assignees": [{"login": "octocat"}]} + ]`)) + default: + http.NotFound(w, r) + } + }) + server := httptest.NewServer(handler) + defer server.Close() + + origHost, origToken, origVersion := flag.Host, flag.Token, flag.Version + flag.Host, flag.Token, flag.Version = server.URL, "", "test" + defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() + + args := map[string]any{ + "owner": owner, "repo": repo, + } + res, err := listRepoIssuesFn(context.Background(), args) + if err != nil { + t.Fatalf("listRepoIssuesFn() error = %v", err) + } + if res.IsError { + t.Fatalf("unexpected error result: %v", res.Content) + } + body := res.Content[0].(*mcp.TextContent).Text + if !strings.Contains(body, `"assignees"`) || !strings.Contains(body, `"octocat"`) { + t.Fatalf("expected assignees in list output, got: %s", body) + } +} + func Test_createIssueFn_labels(t *testing.T) { const ( owner = "octo" diff --git a/operation/issue/slim.go b/operation/issue/slim.go index 84105e4..5c9d53f 100644 --- a/operation/issue/slim.go +++ b/operation/issue/slim.go @@ -63,6 +63,9 @@ func slimIssues(issues []*gitea_sdk.Issue) []map[string]any { if len(i.Labels) > 0 { m["labels"] = slim.LabelNames(i.Labels) } + if len(i.Assignees) > 0 { + m["assignees"] = slim.UserLogins(i.Assignees) + } if i.Milestone != nil { m["milestone"] = map[string]any{ "id": i.Milestone.ID,