@@ -191,10 +191,24 @@ func WithPagination() mcp.ToolOption {
191
191
}
192
192
}
193
193
194
- // WithGraphQLPagination adds GraphQL cursor-based pagination parameters to a tool.
195
- // https://docs.github.com/en/graphql/reference/objects#connection
196
- func WithGraphQLPagination () mcp.ToolOption {
194
+ // WithUnifiedPagination adds both REST and GraphQL pagination parameters to a tool.
195
+ // This allows tools to accept both page/perPage and cursor-based pagination parameters.
196
+ // GraphQL tools should use this and convert page/perPage to first/after internally.
197
+ func WithUnifiedPagination () mcp.ToolOption {
197
198
return func (tool * mcp.Tool ) {
199
+ // REST API pagination parameters
200
+ mcp .WithNumber ("page" ,
201
+ mcp .Description ("Page number for pagination (min 1)" ),
202
+ mcp .Min (1 ),
203
+ )(tool )
204
+
205
+ mcp .WithNumber ("perPage" ,
206
+ mcp .Description ("Results per page for pagination (min 1, max 100)" ),
207
+ mcp .Min (1 ),
208
+ mcp .Max (100 ),
209
+ )(tool )
210
+
211
+ // GraphQL cursor-based pagination parameters
198
212
mcp .WithNumber ("first" ,
199
213
mcp .Description ("Number of items to return per page (min 1, max 100)" ),
200
214
mcp .Min (1 ),
@@ -249,6 +263,110 @@ type GraphQLPaginationParams struct {
249
263
Before * string
250
264
}
251
265
266
+ // UnifiedPaginationParams contains both REST and GraphQL pagination parameters
267
+ type UnifiedPaginationParams struct {
268
+ // REST API pagination
269
+ Page int
270
+ PerPage int
271
+
272
+ // GraphQL cursor-based pagination
273
+ First * int32
274
+ Last * int32
275
+ After * string
276
+ Before * string
277
+ }
278
+
279
+ // ToGraphQLParams converts unified pagination parameters to GraphQL-specific parameters.
280
+ // If cursor-based parameters (first/last/after/before) are provided, they take precedence.
281
+ // Otherwise, page/perPage are converted to first/after equivalent.
282
+ func (u UnifiedPaginationParams ) ToGraphQLParams () GraphQLPaginationParams {
283
+ // If any cursor-based parameters are explicitly set, use them directly
284
+ if u .First != nil || u .Last != nil || u .After != nil || u .Before != nil {
285
+ return GraphQLPaginationParams {
286
+ First : u .First ,
287
+ Last : u .Last ,
288
+ After : u .After ,
289
+ Before : u .Before ,
290
+ }
291
+ }
292
+
293
+ // Convert page/perPage to GraphQL parameters
294
+ // For GraphQL, we use 'first' for perPage and ignore page for the initial request
295
+ // (subsequent requests would use 'after' cursor from previous response)
296
+ first := int32 (u .PerPage )
297
+ return GraphQLPaginationParams {
298
+ First : & first ,
299
+ Last : nil ,
300
+ After : nil ,
301
+ Before : nil ,
302
+ }
303
+ }
304
+
305
+ // OptionalUnifiedPaginationParams returns unified pagination parameters from the request.
306
+ // It accepts both REST API (page/perPage) and GraphQL (first/last/after/before) parameters.
307
+ func OptionalUnifiedPaginationParams (r mcp.CallToolRequest ) (UnifiedPaginationParams , error ) {
308
+ var params UnifiedPaginationParams
309
+
310
+ // Get REST API pagination parameters with defaults
311
+ page , err := OptionalIntParamWithDefault (r , "page" , 1 )
312
+ if err != nil {
313
+ return UnifiedPaginationParams {}, err
314
+ }
315
+ params .Page = page
316
+
317
+ perPage , err := OptionalIntParamWithDefault (r , "perPage" , 30 )
318
+ if err != nil {
319
+ return UnifiedPaginationParams {}, err
320
+ }
321
+ params .PerPage = perPage
322
+
323
+ // Get GraphQL pagination parameters
324
+ if val , err := OptionalParam [float64 ](r , "first" ); err != nil {
325
+ return UnifiedPaginationParams {}, err
326
+ } else if val != 0 {
327
+ first := int32 (val )
328
+ params .First = & first
329
+ }
330
+
331
+ if val , err := OptionalParam [float64 ](r , "last" ); err != nil {
332
+ return UnifiedPaginationParams {}, err
333
+ } else if val != 0 {
334
+ last := int32 (val )
335
+ params .Last = & last
336
+ }
337
+
338
+ if val , err := OptionalParam [string ](r , "after" ); err != nil {
339
+ return UnifiedPaginationParams {}, err
340
+ } else if val != "" {
341
+ params .After = & val
342
+ }
343
+
344
+ if val , err := OptionalParam [string ](r , "before" ); err != nil {
345
+ return UnifiedPaginationParams {}, err
346
+ } else if val != "" {
347
+ params .Before = & val
348
+ }
349
+
350
+ // Validate GraphQL pagination parameters according to GraphQL connection spec
351
+ // Only validate if any GraphQL parameters are provided
352
+ if params .First != nil || params .Last != nil || params .After != nil || params .Before != nil {
353
+ if params .First != nil && params .Last != nil {
354
+ return UnifiedPaginationParams {}, fmt .Errorf ("only one of 'first' or 'last' may be specified" )
355
+ }
356
+ if params .After != nil && params .Before != nil {
357
+ return UnifiedPaginationParams {}, fmt .Errorf ("only one of 'after' or 'before' may be specified" )
358
+ }
359
+ if params .After != nil && params .Last != nil {
360
+ return UnifiedPaginationParams {}, fmt .Errorf ("'after' cannot be used with 'last'. Did you mean to use 'before' instead?" )
361
+ }
362
+ if params .Before != nil && params .First != nil {
363
+ return UnifiedPaginationParams {}, fmt .Errorf ("'before' cannot be used with 'first'. Did you mean to use 'after' instead?" )
364
+ }
365
+ }
366
+
367
+ return params , nil
368
+ }
369
+
252
370
// OptionalGraphQLPaginationParams returns the GraphQL cursor-based pagination parameters from the request.
253
371
// It validates that the parameters are used correctly according to GraphQL connection spec.
254
372
func OptionalGraphQLPaginationParams (r mcp.CallToolRequest ) (GraphQLPaginationParams , error ) {
0 commit comments