Skip to content

Commit 7939297

Browse files
committed
Add pagination helper
1 parent c174179 commit 7939297

File tree

6 files changed

+150
-93
lines changed

6 files changed

+150
-93
lines changed

pkg/github/issues.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,7 @@ func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (
162162
mcp.Description("Sort order ('asc' or 'desc')"),
163163
mcp.Enum("asc", "desc"),
164164
),
165-
mcp.WithNumber("perPage",
166-
mcp.Description("Results per page (max 100)"),
167-
mcp.Min(1),
168-
mcp.Max(100),
169-
),
170-
mcp.WithNumber("page",
171-
mcp.Description("Page number"),
172-
mcp.Min(1),
173-
),
165+
withPagination(),
174166
),
175167
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
176168
query, err := requiredParam[string](request, "q")
@@ -185,11 +177,7 @@ func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (
185177
if err != nil {
186178
return mcp.NewToolResultError(err.Error()), nil
187179
}
188-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
189-
if err != nil {
190-
return mcp.NewToolResultError(err.Error()), nil
191-
}
192-
page, err := optionalIntParamWithDefault(request, "page", 1)
180+
pagination, err := optionalPaginationParams(request)
193181
if err != nil {
194182
return mcp.NewToolResultError(err.Error()), nil
195183
}
@@ -198,8 +186,8 @@ func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (
198186
Sort: sort,
199187
Order: order,
200188
ListOptions: github.ListOptions{
201-
PerPage: perPage,
202-
Page: page,
189+
PerPage: pagination.perPage,
190+
Page: pagination.page,
203191
},
204192
}
205193

@@ -375,12 +363,7 @@ func listIssues(client *github.Client, t translations.TranslationHelperFunc) (to
375363
mcp.WithString("since",
376364
mcp.Description("Filter by date (ISO 8601 timestamp)"),
377365
),
378-
mcp.WithNumber("page",
379-
mcp.Description("Page number"),
380-
),
381-
mcp.WithNumber("perPage",
382-
mcp.Description("Results per page"),
383-
),
366+
withPagination(),
384367
),
385368
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
386369
owner, err := requiredParam[string](request, "owner")

pkg/github/pullrequests.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,7 @@ func listPullRequests(client *github.Client, t translations.TranslationHelperFun
9494
mcp.WithString("direction",
9595
mcp.Description("Sort direction ('asc', 'desc')"),
9696
),
97-
mcp.WithNumber("perPage",
98-
mcp.Description("Results per page (max 100)"),
99-
),
100-
mcp.WithNumber("page",
101-
mcp.Description("Page number"),
102-
),
97+
withPagination(),
10398
),
10499
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
105100
owner, err := requiredParam[string](request, "owner")
@@ -130,11 +125,7 @@ func listPullRequests(client *github.Client, t translations.TranslationHelperFun
130125
if err != nil {
131126
return mcp.NewToolResultError(err.Error()), nil
132127
}
133-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
134-
if err != nil {
135-
return mcp.NewToolResultError(err.Error()), nil
136-
}
137-
page, err := optionalIntParamWithDefault(request, "page", 1)
128+
pagination, err := optionalPaginationParams(request)
138129
if err != nil {
139130
return mcp.NewToolResultError(err.Error()), nil
140131
}
@@ -146,8 +137,8 @@ func listPullRequests(client *github.Client, t translations.TranslationHelperFun
146137
Sort: sort,
147138
Direction: direction,
148139
ListOptions: github.ListOptions{
149-
PerPage: perPage,
150-
Page: page,
140+
PerPage: pagination.perPage,
141+
Page: pagination.page,
151142
},
152143
}
153144

pkg/github/repositories.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ func listCommits(client *github.Client, t translations.TranslationHelperFunc) (t
2929
mcp.WithString("sha",
3030
mcp.Description("Branch name"),
3131
),
32-
mcp.WithNumber("page",
33-
mcp.Description("Page number"),
34-
),
35-
mcp.WithNumber("perPage",
36-
mcp.Description("Number of records per page"),
37-
),
32+
withPagination(),
3833
),
3934
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
4035
owner, err := requiredParam[string](request, "owner")
@@ -49,20 +44,16 @@ func listCommits(client *github.Client, t translations.TranslationHelperFunc) (t
4944
if err != nil {
5045
return mcp.NewToolResultError(err.Error()), nil
5146
}
52-
page, err := optionalIntParamWithDefault(request, "page", 1)
53-
if err != nil {
54-
return mcp.NewToolResultError(err.Error()), nil
55-
}
56-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
47+
pagination, err := optionalPaginationParams(request)
5748
if err != nil {
5849
return mcp.NewToolResultError(err.Error()), nil
5950
}
6051

6152
opts := &github.CommitsListOptions{
6253
SHA: sha,
6354
ListOptions: github.ListOptions{
64-
Page: page,
65-
PerPage: perPage,
55+
Page: pagination.page,
56+
PerPage: pagination.perPage,
6657
},
6758
}
6859

pkg/github/search.go

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,22 @@ func searchRepositories(client *github.Client, t translations.TranslationHelperF
2020
mcp.Required(),
2121
mcp.Description("Search query"),
2222
),
23-
mcp.WithNumber("page",
24-
mcp.Description("Page number for pagination"),
25-
),
26-
mcp.WithNumber("perPage",
27-
mcp.Description("Results per page (max 100)"),
28-
),
23+
withPagination(),
2924
),
3025
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
3126
query, err := requiredParam[string](request, "query")
3227
if err != nil {
3328
return mcp.NewToolResultError(err.Error()), nil
3429
}
35-
page, err := optionalIntParamWithDefault(request, "page", 1)
36-
if err != nil {
37-
return mcp.NewToolResultError(err.Error()), nil
38-
}
39-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
30+
pagination, err := optionalPaginationParams(request)
4031
if err != nil {
4132
return mcp.NewToolResultError(err.Error()), nil
4233
}
4334

4435
opts := &github.SearchOptions{
4536
ListOptions: github.ListOptions{
46-
Page: page,
47-
PerPage: perPage,
37+
Page: pagination.page,
38+
PerPage: pagination.perPage,
4839
},
4940
}
5041

@@ -86,15 +77,7 @@ func searchCode(client *github.Client, t translations.TranslationHelperFunc) (to
8677
mcp.Description("Sort order ('asc' or 'desc')"),
8778
mcp.Enum("asc", "desc"),
8879
),
89-
mcp.WithNumber("perPage",
90-
mcp.Description("Results per page (max 100)"),
91-
mcp.Min(1),
92-
mcp.Max(100),
93-
),
94-
mcp.WithNumber("page",
95-
mcp.Description("Page number"),
96-
mcp.Min(1),
97-
),
80+
withPagination(),
9881
),
9982
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
10083
query, err := requiredParam[string](request, "q")
@@ -109,11 +92,7 @@ func searchCode(client *github.Client, t translations.TranslationHelperFunc) (to
10992
if err != nil {
11093
return mcp.NewToolResultError(err.Error()), nil
11194
}
112-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
113-
if err != nil {
114-
return mcp.NewToolResultError(err.Error()), nil
115-
}
116-
page, err := optionalIntParamWithDefault(request, "page", 1)
95+
pagination, err := optionalPaginationParams(request)
11796
if err != nil {
11897
return mcp.NewToolResultError(err.Error()), nil
11998
}
@@ -122,8 +101,8 @@ func searchCode(client *github.Client, t translations.TranslationHelperFunc) (to
122101
Sort: sort,
123102
Order: order,
124103
ListOptions: github.ListOptions{
125-
PerPage: perPage,
126-
Page: page,
104+
PerPage: pagination.perPage,
105+
Page: pagination.page,
127106
},
128107
}
129108

@@ -166,15 +145,7 @@ func searchUsers(client *github.Client, t translations.TranslationHelperFunc) (t
166145
mcp.Description("Sort order ('asc' or 'desc')"),
167146
mcp.Enum("asc", "desc"),
168147
),
169-
mcp.WithNumber("perPage",
170-
mcp.Description("Results per page (max 100)"),
171-
mcp.Min(1),
172-
mcp.Max(100),
173-
),
174-
mcp.WithNumber("page",
175-
mcp.Description("Page number"),
176-
mcp.Min(1),
177-
),
148+
withPagination(),
178149
),
179150
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
180151
query, err := requiredParam[string](request, "q")
@@ -189,11 +160,7 @@ func searchUsers(client *github.Client, t translations.TranslationHelperFunc) (t
189160
if err != nil {
190161
return mcp.NewToolResultError(err.Error()), nil
191162
}
192-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
193-
if err != nil {
194-
return mcp.NewToolResultError(err.Error()), nil
195-
}
196-
page, err := optionalIntParamWithDefault(request, "page", 1)
163+
pagination, err := optionalPaginationParams(request)
197164
if err != nil {
198165
return mcp.NewToolResultError(err.Error()), nil
199166
}
@@ -202,8 +169,8 @@ func searchUsers(client *github.Client, t translations.TranslationHelperFunc) (t
202169
Sort: sort,
203170
Order: order,
204171
ListOptions: github.ListOptions{
205-
PerPage: perPage,
206-
Page: page,
172+
PerPage: pagination.perPage,
173+
Page: pagination.page,
207174
},
208175
}
209176

pkg/github/server.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,45 @@ func optionalStringArrayParam(r mcp.CallToolRequest, p string) ([]string, error)
229229
return []string{}, fmt.Errorf("parameter %s could not be coerced to []string, is %T", p, r.Params.Arguments[p])
230230
}
231231
}
232+
233+
// withPagination returns a ToolOption that adds "page" and "perPage" parameters to the tool.
234+
// The "page" parameter is optional, min 1. The "perPage" parameter is optional, min 1, max 100.
235+
func withPagination() mcp.ToolOption {
236+
return func(tool *mcp.Tool) {
237+
mcp.WithNumber("page",
238+
mcp.Description("Page number for pagination (min 1)"),
239+
mcp.Min(1),
240+
)(tool)
241+
242+
mcp.WithNumber("perPage",
243+
mcp.Description("Results per page for pagination (min 1, max 100)"),
244+
mcp.Min(1),
245+
mcp.Max(100),
246+
)(tool)
247+
}
248+
}
249+
250+
type paginationParams struct {
251+
page int
252+
perPage int
253+
}
254+
255+
// optionalPaginationParams returns the "page" and "perPage" parameters from the request,
256+
// or their default values if not present, "page" default is 1, "perPage" default is 30.
257+
// In future, we may want to make the default values configurable, or even have this
258+
// function returned from `withPagination`, where the defaults are provided alongside
259+
// the min/max values.
260+
func optionalPaginationParams(r mcp.CallToolRequest) (paginationParams, error) {
261+
page, err := optionalIntParamWithDefault(r, "page", 1)
262+
if err != nil {
263+
return paginationParams{}, err
264+
}
265+
perPage, err := optionalIntParamWithDefault(r, "perPage", 30)
266+
if err != nil {
267+
return paginationParams{}, err
268+
}
269+
return paginationParams{
270+
page: page,
271+
perPage: perPage,
272+
}, nil
273+
}

pkg/github/server_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,3 +551,86 @@ func TestOptionalStringArrayParam(t *testing.T) {
551551
})
552552
}
553553
}
554+
555+
func TestOptionalPaginationParams(t *testing.T) {
556+
tests := []struct {
557+
name string
558+
params map[string]any
559+
expected paginationParams
560+
expectError bool
561+
}{
562+
{
563+
name: "no pagination parameters, default values",
564+
params: map[string]any{},
565+
expected: paginationParams{
566+
page: 1,
567+
perPage: 30,
568+
},
569+
expectError: false,
570+
},
571+
{
572+
name: "page parameter, default perPage",
573+
params: map[string]any{
574+
"page": float64(2),
575+
},
576+
expected: paginationParams{
577+
page: 2,
578+
perPage: 30,
579+
},
580+
expectError: false,
581+
},
582+
{
583+
name: "perPage parameter, default page",
584+
params: map[string]any{
585+
"perPage": float64(50),
586+
},
587+
expected: paginationParams{
588+
page: 1,
589+
perPage: 50,
590+
},
591+
expectError: false,
592+
},
593+
{
594+
name: "page and perPage parameters",
595+
params: map[string]any{
596+
"page": float64(2),
597+
"perPage": float64(50),
598+
},
599+
expected: paginationParams{
600+
page: 2,
601+
perPage: 50,
602+
},
603+
expectError: false,
604+
},
605+
{
606+
name: "invalid page parameter",
607+
params: map[string]any{
608+
"page": "not-a-number",
609+
},
610+
expected: paginationParams{},
611+
expectError: true,
612+
},
613+
{
614+
name: "invalid perPage parameter",
615+
params: map[string]any{
616+
"perPage": "not-a-number",
617+
},
618+
expected: paginationParams{},
619+
expectError: true,
620+
},
621+
}
622+
623+
for _, tc := range tests {
624+
t.Run(tc.name, func(t *testing.T) {
625+
request := createMCPRequest(tc.params)
626+
result, err := optionalPaginationParams(request)
627+
628+
if tc.expectError {
629+
assert.Error(t, err)
630+
} else {
631+
assert.NoError(t, err)
632+
assert.Equal(t, tc.expected, result)
633+
}
634+
})
635+
}
636+
}

0 commit comments

Comments
 (0)