From 6db9ff619353e4112b650bd2aba6f89ad1464cb3 Mon Sep 17 00:00:00 2001 From: MayorFaj Date: Tue, 9 Jun 2026 04:22:51 +0100 Subject: [PATCH 1/2] feat: implement cursor pagination for dependabot alerts and update related documentation --- README.md | 2 +- .../__toolsnaps__/list_dependabot_alerts.snap | 9 ++- pkg/github/dependabot.go | 19 ++++-- pkg/github/dependabot_test.go | 61 ++++++++++++++----- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index dff62321b8..5e61fde498 100644 --- a/README.md +++ b/README.md @@ -717,8 +717,8 @@ The following sets of tools are available: - **list_dependabot_alerts** - List dependabot alerts - **Required OAuth Scopes**: `security_events` - **Accepted OAuth Scopes**: `repo`, `security_events` + - `after`: Forward pagination cursor from the previous response's pageInfo.nextCursor. (string, optional) - `owner`: The owner of the repository. (string, required) - - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: The name of the repository. (string, required) - `severity`: Filter dependabot alerts by severity (string, optional) diff --git a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap index 55d5437796..fa45c8f6e7 100644 --- a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap +++ b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap @@ -6,15 +6,14 @@ "description": "List dependabot alerts in a GitHub repository.", "inputSchema": { "properties": { + "after": { + "description": "Forward pagination cursor from the previous response's pageInfo.nextCursor.", + "type": "string" + }, "owner": { "description": "The owner of the repository.", "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, diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 02023da69f..99af4fcbd2 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -120,7 +120,11 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server }, Required: []string{"owner", "repo"}, } - WithPagination(schema) + WithCursorPagination(schema) + // The Dependabot alerts REST endpoint uses cursor pagination via the response's + // Link header (surfaced as pageInfo.nextCursor), not GraphQL. Override the shared + // cursor description, which otherwise refers to a GraphQL PageInfo. + schema.Properties["after"].Description = "Forward pagination cursor from the previous response's pageInfo.nextCursor." return NewTool( ToolsetMetadataDependabot, @@ -152,7 +156,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server return utils.NewToolResultError(err.Error()), nil, nil } - pagination, err := OptionalPaginationParams(args) + pagination, err := OptionalCursorPaginationParams(args) if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } @@ -165,9 +169,9 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server alerts, resp, err := client.Dependabot.ListRepoAlerts(ctx, owner, repo, &github.ListAlertsOptions{ State: ToStringPtr(state), Severity: ToStringPtr(severity), - ListOptions: github.ListOptions{ - Page: pagination.Page, + ListCursorOptions: github.ListCursorOptions{ PerPage: pagination.PerPage, + After: pagination.After, }, }) if err != nil { @@ -187,7 +191,12 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list alerts", resp, body), nil, nil } - r, err := json.Marshal(alerts) + response := map[string]any{ + "alerts": alerts, + "pageInfo": buildPageInfo(resp), + } + + r, err := json.Marshal(response) if err != nil { return utils.NewToolResultErrorFromErr("failed to marshal alerts", err), nil, err } diff --git a/pkg/github/dependabot_test.go b/pkg/github/dependabot_test.go index 7811483908..5236c6d349 100644 --- a/pkg/github/dependabot_test.go +++ b/pkg/github/dependabot_test.go @@ -154,19 +154,19 @@ func Test_ListDependabotAlerts(t *testing.T) { } tests := []struct { - name string - mockedClient *http.Client - requestArgs map[string]any - expectError bool - expectedAlerts []*github.DependabotAlert - expectedErrMsg string + name string + mockedClient *http.Client + requestArgs map[string]any + expectError bool + expectedAlerts []*github.DependabotAlert + expectedNextCursor string + expectedErrMsg string }{ { name: "successful open alerts listing", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ "state": "open", - "page": "1", "per_page": "30", }).andThen( mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}), @@ -185,7 +185,6 @@ func Test_ListDependabotAlerts(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ "severity": "high", - "page": "1", "per_page": "30", }).andThen( mockResponse(t, http.StatusOK, []*github.DependabotAlert{&highSeverityAlert}), @@ -203,7 +202,6 @@ func Test_ListDependabotAlerts(t *testing.T) { name: "successful all alerts listing", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ - "page": "1", "per_page": "30", }).andThen( mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert, &highSeverityAlert}), @@ -217,10 +215,10 @@ func Test_ListDependabotAlerts(t *testing.T) { expectedAlerts: []*github.DependabotAlert{&criticalAlert, &highSeverityAlert}, }, { - name: "successful alerts listing with custom pagination", + name: "successful alerts listing with cursor pagination", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ - "page": "3", + "after": "Y3Vyc29yOnYyOpK5", "per_page": "100", }).andThen( mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}), @@ -229,12 +227,35 @@ func Test_ListDependabotAlerts(t *testing.T) { requestArgs: map[string]any{ "owner": "owner", "repo": "repo", - "page": float64(3), + "after": "Y3Vyc29yOnYyOpK5", "perPage": float64(100), }, expectError: false, expectedAlerts: []*github.DependabotAlert{&criticalAlert}, }, + { + name: "successful alerts listing surfaces next page cursor", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "per_page": "30", + }).andThen( + func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Link", `; rel="next"`) + w.WriteHeader(http.StatusOK) + b, err := json.Marshal([]*github.DependabotAlert{&criticalAlert}) + require.NoError(t, err) + _, _ = w.Write(b) + }, + ), + }), + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + }, + expectError: false, + expectedAlerts: []*github.DependabotAlert{&criticalAlert}, + expectedNextCursor: "nextcursor123", + }, { name: "alerts listing fails", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -291,11 +312,17 @@ func Test_ListDependabotAlerts(t *testing.T) { textContent := getTextResult(t, result) // Unmarshal and verify the result - var returnedAlerts []*github.DependabotAlert - err = json.Unmarshal([]byte(textContent.Text), &returnedAlerts) + var returnedResult struct { + Alerts []*github.DependabotAlert `json:"alerts"` + PageInfo struct { + HasNextPage bool `json:"hasNextPage"` + NextCursor string `json:"nextCursor"` + } `json:"pageInfo"` + } + err = json.Unmarshal([]byte(textContent.Text), &returnedResult) assert.NoError(t, err) - assert.Len(t, returnedAlerts, len(tc.expectedAlerts)) - for i, alert := range returnedAlerts { + assert.Len(t, returnedResult.Alerts, len(tc.expectedAlerts)) + for i, alert := range returnedResult.Alerts { assert.Equal(t, *tc.expectedAlerts[i].Number, *alert.Number) assert.Equal(t, *tc.expectedAlerts[i].HTMLURL, *alert.HTMLURL) assert.Equal(t, *tc.expectedAlerts[i].State, *alert.State) @@ -304,6 +331,8 @@ func Test_ListDependabotAlerts(t *testing.T) { assert.Equal(t, *tc.expectedAlerts[i].SecurityAdvisory.Severity, *alert.SecurityAdvisory.Severity) } } + assert.Equal(t, tc.expectedNextCursor, returnedResult.PageInfo.NextCursor) + assert.Equal(t, tc.expectedNextCursor != "", returnedResult.PageInfo.HasNextPage) }) } } From 1a4f4e06debddd2cbdf9249b89155c65a8255761 Mon Sep 17 00:00:00 2001 From: MayorFaj Date: Tue, 9 Jun 2026 04:41:28 +0100 Subject: [PATCH 2/2] fix: update cursor pagination description for dependabot alerts --- README.md | 2 +- pkg/github/__toolsnaps__/list_dependabot_alerts.snap | 2 +- pkg/github/dependabot.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5e61fde498..2ef367041c 100644 --- a/README.md +++ b/README.md @@ -717,7 +717,7 @@ The following sets of tools are available: - **list_dependabot_alerts** - List dependabot alerts - **Required OAuth Scopes**: `security_events` - **Accepted OAuth Scopes**: `repo`, `security_events` - - `after`: Forward pagination cursor from the previous response's pageInfo.nextCursor. (string, optional) + - `after`: Omit for the first page. For subsequent pages, use pageInfo.nextCursor from the previous response. (string, optional) - `owner`: The owner of the repository. (string, required) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: The name of the repository. (string, required) diff --git a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap index fa45c8f6e7..7779e46a4a 100644 --- a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap +++ b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap @@ -7,7 +7,7 @@ "inputSchema": { "properties": { "after": { - "description": "Forward pagination cursor from the previous response's pageInfo.nextCursor.", + "description": "Omit for the first page. For subsequent pages, use pageInfo.nextCursor from the previous response.", "type": "string" }, "owner": { diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 99af4fcbd2..62d38974b8 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -124,7 +124,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server // The Dependabot alerts REST endpoint uses cursor pagination via the response's // Link header (surfaced as pageInfo.nextCursor), not GraphQL. Override the shared // cursor description, which otherwise refers to a GraphQL PageInfo. - schema.Properties["after"].Description = "Forward pagination cursor from the previous response's pageInfo.nextCursor." + schema.Properties["after"].Description = "Omit for the first page. For subsequent pages, use pageInfo.nextCursor from the previous response." return NewTool( ToolsetMetadataDependabot,