feat(issue): add milestones and type filters to list_issues (#199)
## Summary
`list_issues` could not be scoped to a milestone, and the milestone was
absent from its list output. To find which milestone an issue belongs to,
callers had to read every issue individually — expensive on repositories
with hundreds of issues.
## Changes
- `list_issues`: add `milestones` (name or ID filter) and `type`
(`issues`/`pulls`) parameters, wiring the SDK's
`ListIssueOption.Milestones` and `ListIssueOption.Type`.
- Include the `milestone` `{id, title}` field in `slimIssues` (the list
output), consistent with the existing `slimIssue` (single-issue) output.
- Tests: extend `Test_listRepoIssuesFn_filters` to assert the `milestones`
and `type` query params, and add `Test_listRepoIssuesFn_includesMilestone`
for the milestone field in list output.
This is a natural continuation of #164 (which added `labels`/`since`/`before`
to `list_issues`); `milestones` and `type` are supported by the SDK but were
not yet wired through.
## Testing
- `go build ./...` — ok
- `go vet ./operation/issue/` — ok
- `go test ./...` — all pass
- `make fmt` — clean
---
This PR was written with the help of Claude Opus 4.8
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/199
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: jermey <186737+jermey@noreply.gitea.com>
Co-committed-by: jermey <186737+jermey@noreply.gitea.com>
This commit is contained in:
@@ -63,10 +63,12 @@ func Test_listRepoIssuesFn_filters(t *testing.T) {
|
||||
req := mcp.CallToolRequest{
|
||||
Params: mcp.CallToolParams{
|
||||
Arguments: map[string]any{
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"labels": []any{"bug", "enhancement"},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -85,6 +87,59 @@ func Test_listRepoIssuesFn_filters(t *testing.T) {
|
||||
if !strings.Contains(gotQuery, "since=2026-01-01") {
|
||||
t.Fatalf("expected since query param, got %s", gotQuery)
|
||||
}
|
||||
if !strings.Contains(gotQuery, "milestones=v1.0%2C2") {
|
||||
t.Fatalf("expected milestones query param, got %s", gotQuery)
|
||||
}
|
||||
if !strings.Contains(gotQuery, "type=issues") {
|
||||
t.Fatalf("expected type query param, got %s", gotQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listRepoIssuesFn_includesMilestone(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 milestone", "state": "closed", "milestone": {"id": 5, "title": "v1.0"}},
|
||||
{"number": 2, "title": "without milestone", "state": "open"}
|
||||
]`))
|
||||
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 }()
|
||||
|
||||
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
"owner": owner, "repo": repo,
|
||||
}}}
|
||||
res, err := listRepoIssuesFn(context.Background(), req)
|
||||
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, `"milestone"`) || !strings.Contains(body, `"v1.0"`) {
|
||||
t.Fatalf("expected milestone in list output, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createIssueFn_labels(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user