Skip to content

Commit 0bd0bf0

Browse files
dviraradSamMorrowDrumssammorrowdrumsCopilot
authored
feat: add pagination to list GHAS alerts tools (#2451)
* feat(code_scanning): add pagination to list_code_scanning_alerts (#2363) * feat(dependabot): add pagination to list_dependabot_alerts (#2363) * feat(secret_scanning): add pagination to list_secret_scanning_alerts (#2363) * test(code_scanning): pagination expectations + new test case (#2363) * test(dependabot): pagination expectations + new test case (#2363) * test(secret_scanning): pagination expectations + new test case (#2363) * test(toolsnaps): refresh list_code_scanning_alerts with page/perPage (#2363) * test(toolsnaps): refresh list_dependabot_alerts with page/perPage (#2363) * test(toolsnaps): refresh list_secret_scanning_alerts with page/perPage (#2363) * docs: regenerate README for new pagination params Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Sam Morrow <info@sam-morrow.com> Co-authored-by: sammorrowdrums <sam.morrowdrums@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8f4680b commit 0bd0bf0

10 files changed

Lines changed: 249 additions & 92 deletions

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,8 @@ The following sets of tools are available:
649649
- **Required OAuth Scopes**: `security_events`
650650
- **Accepted OAuth Scopes**: `repo`, `security_events`
651651
- `owner`: The owner of the repository. (string, required)
652+
- `page`: Page number for pagination (min 1) (number, optional)
653+
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
652654
- `ref`: The Git reference for the results you want to list. (string, optional)
653655
- `repo`: The name of the repository. (string, required)
654656
- `severity`: Filter code scanning alerts by severity (string, optional)
@@ -712,6 +714,8 @@ The following sets of tools are available:
712714
- **Required OAuth Scopes**: `security_events`
713715
- **Accepted OAuth Scopes**: `repo`, `security_events`
714716
- `owner`: The owner of the repository. (string, required)
717+
- `page`: Page number for pagination (min 1) (number, optional)
718+
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
715719
- `repo`: The name of the repository. (string, required)
716720
- `severity`: Filter dependabot alerts by severity (string, optional)
717721
- `state`: Filter dependabot alerts by state. Defaults to open (string, optional)
@@ -1324,6 +1328,8 @@ The following sets of tools are available:
13241328
- **Required OAuth Scopes**: `security_events`
13251329
- **Accepted OAuth Scopes**: `repo`, `security_events`
13261330
- `owner`: The owner of the repository. (string, required)
1331+
- `page`: Page number for pagination (min 1) (number, optional)
1332+
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
13271333
- `repo`: The name of the repository. (string, required)
13281334
- `resolution`: Filter by resolution (string, optional)
13291335
- `secret_type`: A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter. (string, optional)

pkg/github/__toolsnaps__/list_code_scanning_alerts.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
"description": "The owner of the repository.",
1111
"type": "string"
1212
},
13+
"page": {
14+
"description": "Page number for pagination (min 1)",
15+
"minimum": 1,
16+
"type": "number"
17+
},
18+
"perPage": {
19+
"description": "Results per page for pagination (min 1, max 100)",
20+
"maximum": 100,
21+
"minimum": 1,
22+
"type": "number"
23+
},
1324
"ref": {
1425
"description": "The Git reference for the results you want to list.",
1526
"type": "string"

pkg/github/__toolsnaps__/list_dependabot_alerts.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
"description": "The owner of the repository.",
1111
"type": "string"
1212
},
13+
"page": {
14+
"description": "Page number for pagination (min 1)",
15+
"minimum": 1,
16+
"type": "number"
17+
},
18+
"perPage": {
19+
"description": "Results per page for pagination (min 1, max 100)",
20+
"maximum": 100,
21+
"minimum": 1,
22+
"type": "number"
23+
},
1324
"repo": {
1425
"description": "The name of the repository.",
1526
"type": "string"

pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
"description": "The owner of the repository.",
1111
"type": "string"
1212
},
13+
"page": {
14+
"description": "Page number for pagination (min 1)",
15+
"minimum": 1,
16+
"type": "number"
17+
},
18+
"perPage": {
19+
"description": "Results per page for pagination (min 1, max 100)",
20+
"maximum": 100,
21+
"minimum": 1,
22+
"type": "number"
23+
},
1324
"repo": {
1425
"description": "The name of the repository.",
1526
"type": "string"

pkg/github/code_scanning.go

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,41 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server
9494
}
9595

9696
func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.ServerTool {
97+
schema := &jsonschema.Schema{
98+
Type: "object",
99+
Properties: map[string]*jsonschema.Schema{
100+
"owner": {
101+
Type: "string",
102+
Description: "The owner of the repository.",
103+
},
104+
"repo": {
105+
Type: "string",
106+
Description: "The name of the repository.",
107+
},
108+
"state": {
109+
Type: "string",
110+
Description: "Filter code scanning alerts by state. Defaults to open",
111+
Enum: []any{"open", "closed", "dismissed", "fixed"},
112+
Default: json.RawMessage(`"open"`),
113+
},
114+
"ref": {
115+
Type: "string",
116+
Description: "The Git reference for the results you want to list.",
117+
},
118+
"severity": {
119+
Type: "string",
120+
Description: "Filter code scanning alerts by severity",
121+
Enum: []any{"critical", "high", "medium", "low", "warning", "note", "error"},
122+
},
123+
"tool_name": {
124+
Type: "string",
125+
Description: "The name of the tool used for code scanning.",
126+
},
127+
},
128+
Required: []string{"owner", "repo"},
129+
}
130+
WithPagination(schema)
131+
97132
return NewTool(
98133
ToolsetMetadataCodeSecurity,
99134
mcp.Tool{
@@ -103,39 +138,7 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv
103138
Title: t("TOOL_LIST_CODE_SCANNING_ALERTS_USER_TITLE", "List code scanning alerts"),
104139
ReadOnlyHint: true,
105140
},
106-
InputSchema: &jsonschema.Schema{
107-
Type: "object",
108-
Properties: map[string]*jsonschema.Schema{
109-
"owner": {
110-
Type: "string",
111-
Description: "The owner of the repository.",
112-
},
113-
"repo": {
114-
Type: "string",
115-
Description: "The name of the repository.",
116-
},
117-
"state": {
118-
Type: "string",
119-
Description: "Filter code scanning alerts by state. Defaults to open",
120-
Enum: []any{"open", "closed", "dismissed", "fixed"},
121-
Default: json.RawMessage(`"open"`),
122-
},
123-
"ref": {
124-
Type: "string",
125-
Description: "The Git reference for the results you want to list.",
126-
},
127-
"severity": {
128-
Type: "string",
129-
Description: "Filter code scanning alerts by severity",
130-
Enum: []any{"critical", "high", "medium", "low", "warning", "note", "error"},
131-
},
132-
"tool_name": {
133-
Type: "string",
134-
Description: "The name of the tool used for code scanning.",
135-
},
136-
},
137-
Required: []string{"owner", "repo"},
138-
},
141+
InputSchema: schema,
139142
},
140143
[]scopes.Scope{scopes.SecurityEvents},
141144
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
@@ -164,11 +167,25 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv
164167
return utils.NewToolResultError(err.Error()), nil, nil
165168
}
166169

170+
pagination, err := OptionalPaginationParams(args)
171+
if err != nil {
172+
return utils.NewToolResultError(err.Error()), nil, nil
173+
}
174+
167175
client, err := deps.GetClient(ctx)
168176
if err != nil {
169177
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
170178
}
171-
alerts, resp, err := client.CodeScanning.ListAlertsForRepo(ctx, owner, repo, &github.AlertListOptions{Ref: ref, State: state, Severity: severity, ToolName: toolName})
179+
alerts, resp, err := client.CodeScanning.ListAlertsForRepo(ctx, owner, repo, &github.AlertListOptions{
180+
Ref: ref,
181+
State: state,
182+
Severity: severity,
183+
ToolName: toolName,
184+
ListOptions: github.ListOptions{
185+
Page: pagination.Page,
186+
PerPage: pagination.PerPage,
187+
},
188+
})
172189
if err != nil {
173190
return ghErrors.NewGitHubAPIErrorResponse(ctx,
174191
"failed to list alerts",

pkg/github/code_scanning_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ func Test_ListCodeScanningAlerts(t *testing.T) {
137137
assert.Contains(t, schema.Properties, "state")
138138
assert.Contains(t, schema.Properties, "severity")
139139
assert.Contains(t, schema.Properties, "tool_name")
140+
assert.Contains(t, schema.Properties, "page")
141+
assert.Contains(t, schema.Properties, "perPage")
140142
assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})
141143

142144
// Setup mock alerts for success case
@@ -171,6 +173,8 @@ func Test_ListCodeScanningAlerts(t *testing.T) {
171173
"state": "open",
172174
"severity": "high",
173175
"tool_name": "codeql",
176+
"page": "1",
177+
"per_page": "30",
174178
}).andThen(
175179
mockResponse(t, http.StatusOK, mockAlerts),
176180
),
@@ -186,6 +190,25 @@ func Test_ListCodeScanningAlerts(t *testing.T) {
186190
expectError: false,
187191
expectedAlerts: mockAlerts,
188192
},
193+
{
194+
name: "successful alerts listing with custom pagination",
195+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
196+
GetReposCodeScanningAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{
197+
"page": "2",
198+
"per_page": "50",
199+
}).andThen(
200+
mockResponse(t, http.StatusOK, mockAlerts),
201+
),
202+
}),
203+
requestArgs: map[string]any{
204+
"owner": "owner",
205+
"repo": "repo",
206+
"page": float64(2),
207+
"perPage": float64(50),
208+
},
209+
expectError: false,
210+
expectedAlerts: mockAlerts,
211+
},
189212
{
190213
name: "alerts listing fails",
191214
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{

pkg/github/dependabot.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,33 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo
9595
}
9696

9797
func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.ServerTool {
98+
schema := &jsonschema.Schema{
99+
Type: "object",
100+
Properties: map[string]*jsonschema.Schema{
101+
"owner": {
102+
Type: "string",
103+
Description: "The owner of the repository.",
104+
},
105+
"repo": {
106+
Type: "string",
107+
Description: "The name of the repository.",
108+
},
109+
"state": {
110+
Type: "string",
111+
Description: "Filter dependabot alerts by state. Defaults to open",
112+
Enum: []any{"open", "fixed", "dismissed", "auto_dismissed"},
113+
Default: json.RawMessage(`"open"`),
114+
},
115+
"severity": {
116+
Type: "string",
117+
Description: "Filter dependabot alerts by severity",
118+
Enum: []any{"low", "medium", "high", "critical"},
119+
},
120+
},
121+
Required: []string{"owner", "repo"},
122+
}
123+
WithPagination(schema)
124+
98125
return NewTool(
99126
ToolsetMetadataDependabot,
100127
mcp.Tool{
@@ -104,31 +131,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
104131
Title: t("TOOL_LIST_DEPENDABOT_ALERTS_USER_TITLE", "List dependabot alerts"),
105132
ReadOnlyHint: true,
106133
},
107-
InputSchema: &jsonschema.Schema{
108-
Type: "object",
109-
Properties: map[string]*jsonschema.Schema{
110-
"owner": {
111-
Type: "string",
112-
Description: "The owner of the repository.",
113-
},
114-
"repo": {
115-
Type: "string",
116-
Description: "The name of the repository.",
117-
},
118-
"state": {
119-
Type: "string",
120-
Description: "Filter dependabot alerts by state. Defaults to open",
121-
Enum: []any{"open", "fixed", "dismissed", "auto_dismissed"},
122-
Default: json.RawMessage(`"open"`),
123-
},
124-
"severity": {
125-
Type: "string",
126-
Description: "Filter dependabot alerts by severity",
127-
Enum: []any{"low", "medium", "high", "critical"},
128-
},
129-
},
130-
Required: []string{"owner", "repo"},
131-
},
134+
InputSchema: schema,
132135
},
133136
[]scopes.Scope{scopes.SecurityEvents},
134137
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
@@ -149,6 +152,11 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
149152
return utils.NewToolResultError(err.Error()), nil, nil
150153
}
151154

155+
pagination, err := OptionalPaginationParams(args)
156+
if err != nil {
157+
return utils.NewToolResultError(err.Error()), nil, nil
158+
}
159+
152160
client, err := deps.GetClient(ctx)
153161
if err != nil {
154162
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, err
@@ -157,6 +165,10 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
157165
alerts, resp, err := client.Dependabot.ListRepoAlerts(ctx, owner, repo, &github.ListAlertsOptions{
158166
State: ToStringPtr(state),
159167
Severity: ToStringPtr(severity),
168+
ListOptions: github.ListOptions{
169+
Page: pagination.Page,
170+
PerPage: pagination.PerPage,
171+
},
160172
})
161173
if err != nil {
162174
return ghErrors.NewGitHubAPIErrorResponse(ctx,

pkg/github/dependabot_test.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ func Test_ListDependabotAlerts(t *testing.T) {
165165
name: "successful open alerts listing",
166166
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
167167
GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{
168-
"state": "open",
168+
"state": "open",
169+
"page": "1",
170+
"per_page": "30",
169171
}).andThen(
170172
mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}),
171173
),
@@ -183,6 +185,8 @@ func Test_ListDependabotAlerts(t *testing.T) {
183185
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
184186
GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{
185187
"severity": "high",
188+
"page": "1",
189+
"per_page": "30",
186190
}).andThen(
187191
mockResponse(t, http.StatusOK, []*github.DependabotAlert{&highSeverityAlert}),
188192
),
@@ -198,7 +202,10 @@ func Test_ListDependabotAlerts(t *testing.T) {
198202
{
199203
name: "successful all alerts listing",
200204
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
201-
GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{}).andThen(
205+
GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{
206+
"page": "1",
207+
"per_page": "30",
208+
}).andThen(
202209
mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert, &highSeverityAlert}),
203210
),
204211
}),
@@ -209,6 +216,25 @@ func Test_ListDependabotAlerts(t *testing.T) {
209216
expectError: false,
210217
expectedAlerts: []*github.DependabotAlert{&criticalAlert, &highSeverityAlert},
211218
},
219+
{
220+
name: "successful alerts listing with custom pagination",
221+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
222+
GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{
223+
"page": "3",
224+
"per_page": "100",
225+
}).andThen(
226+
mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}),
227+
),
228+
}),
229+
requestArgs: map[string]any{
230+
"owner": "owner",
231+
"repo": "repo",
232+
"page": float64(3),
233+
"perPage": float64(100),
234+
},
235+
expectError: false,
236+
expectedAlerts: []*github.DependabotAlert{&criticalAlert},
237+
},
212238
{
213239
name: "alerts listing fails",
214240
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{

0 commit comments

Comments
 (0)