Skip to content

Commit 0f9ef6e

Browse files
committed
Add pagination helper
1 parent a720700 commit 0f9ef6e

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
@@ -28,12 +28,7 @@ func listCommits(client *github.Client, t translations.TranslationHelperFunc) (t
2828
mcp.WithString("sha",
2929
mcp.Description("Branch name"),
3030
),
31-
mcp.WithNumber("page",
32-
mcp.Description("Page number"),
33-
),
34-
mcp.WithNumber("perPage",
35-
mcp.Description("Number of records per page"),
36-
),
31+
withPagination(),
3732
),
3833
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
3934
owner, err := requiredParam[string](request, "owner")
@@ -48,20 +43,16 @@ func listCommits(client *github.Client, t translations.TranslationHelperFunc) (t
4843
if err != nil {
4944
return mcp.NewToolResultError(err.Error()), nil
5045
}
51-
page, err := optionalIntParamWithDefault(request, "page", 1)
52-
if err != nil {
53-
return mcp.NewToolResultError(err.Error()), nil
54-
}
55-
perPage, err := optionalIntParamWithDefault(request, "perPage", 30)
46+
pagination, err := optionalPaginationParams(request)
5647
if err != nil {
5748
return mcp.NewToolResultError(err.Error()), nil
5849
}
5950

6051
opts := &github.CommitsListOptions{
6152
SHA: sha,
6253
ListOptions: github.ListOptions{
63-
Page: page,
64-
PerPage: perPage,
54+
Page: pagination.page,
55+
PerPage: pagination.perPage,
6556
},
6657
}
6758

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

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)