Add resolve_pagination_direction to CursorPagination concern

What does this MR do and why?

GPT-based MCP clients serialize every optional parameter, so both first and last arrive simultaneously on bidirectional pagination tools. GraphQL rejects the combination.

  • Adds resolve_pagination_direction to the CursorPagination concern: forward wins when both are present, backward only when last arrives without first. All GraphQL-backed tools now call it instead of passing raw params to GraphQL.
  • Drops "Default 20" / "Default 100" from pagination schema descriptions. GPT copies any concrete value it sees into the request, so mentioning a default causes it to always send first: 20. The server-side default (paginated_first) still applies when no value is sent.
  • Removes the now-unused default_page_size: parameter from input_schema_params and its 6 call sites.

References

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

Manual testing with GDK + Duo chat and GPT-5.4

All tests run against GDK with branch tchu-621520-resolve-pagination-direction. GPT still fills every optional param (empty strings, type defaults) — the "Default N" removal didn't change that behavior.

Also tested manually with Sonnet 4.6 to make sure these all still work (they do).

1. get_merge_request_notes — MR !7

Prompt: "Use the get_merge_request_notes tool to get notes on merge request !7 (merged) in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 100, last: 100, after: "", before: "", url: "" ❌ ToolException
this branch first: 100, last: 100, after: "", before: "", url: "" ✅ Returned 11 notes

2. get_workitem_notes — WI #2

Prompt: "Use the get_workitem_notes tool to get notes on work item #2 (closed) in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 100, last: 1, after: "", before: "", group_id: "", url: "" ❌ ToolException
this branch first: 100, last: 100, after: "", before: "", group_id: "", url: "" ✅ Returned 13 notes

3. list_commits

Prompt: "Use the list_commits tool to list commits in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 20, after: "", author: "", first_parent: false, order: "date", path: "", ref_name: "", since: "", until: "", url: "", with_stats: false ✅ Returned 20 commits
this branch (identical) ✅ Same

GPT sent first: 20 — removing "Default 20" from the description didn't stop it from filling the param.

4. list_merge_requests

Prompt: "Use the list_merge_requests tool to list merge requests in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 20, after: "", scope: "created_by_me", state: "all", search: "", ... ✅ 0 results (scope filter)

GPT defaults scope to created_by_me — returns empty because the root user has no MRs. Separate GPT behavior issue.

5. get_merge_request_notes — "last 5 notes" on MR !7 (merged)

Prompt: "Use the get_merge_request_notes tool to get the last 5 notes on merge request !7 (merged) in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 100, last: 5, after: "", before: "", url: "" ❌ ToolException
this branch first: 5, last: 5, after: "", before: "", url: "" ✅ Returned 5 notes (hasNextPage: true, 11 total)

On master, GraphQL rejects first + last together. On the branch, resolve_pagination_direction picks forward and succeeds.

6. get_merge_request_notes — "first 5 notes" on MR !1 (merged)

Prompt: "Use the get_merge_request_notes tool to get the first 5 notes on merge request !1 (merged) in gitlab-org/gitlab-shell"

Attempt Params sent Result
master first: 5, last: 1, after: "", before: "", url: "" ❌ ToolException
this branch first: 5, last: 100, after: "", before: "", url: "" ✅ Returned 5 notes

Key observations

  • GPT fills every optional param with type defaults (first: 20 or 100, last: 1 or 100, after: "", etc.) regardless of schema description changes
  • resolve_pagination_direction works correctly — when both first and last are present, forward wins and GraphQL gets only one direction
  • Empty string params (after: "", before: "", url: "") are harmless — they don't cause failures once resolve_pagination_direction is active
  • list_merge_requests returning 0 results is a GPT behavior issue (it defaults scope to created_by_me)

Automated script

tested with $6049667

Before

>> Minting a local-GDK PAT (api+mcp)...
>> Preflight OK. Project: top-level-public/top-project-public (39)
>> Seeding test MR...
   Created MR !27
>> Seeding test issue (work item) with notes...
   Created issue #4
   Added 5 notes to issue

==================================================================
A. list_merge_requests — forward pagination (sanity check)
   (forward-only tool: schema only has first/after, GPT won't send last)
==================================================================
   PASS A.1 first=2 returns success
   PASS A.2 returned 0 MRs (<=2)

==================================================================
B. list_merge_requests — cursor follow (page 1 then page 2)
==================================================================
   PASS B.1 page 1 (first=1)
   PASS B.2 page 2 (after cursor)
   (B.3 skipped — project has only one MR, both pages return !)

==================================================================
C. get_workitem_notes — forward pagination (first only)
==================================================================
   PASS C.1 first=2 returns success
   PASS C.2 returned 2 notes (>=1)

==================================================================
D. get_workitem_notes — backward pagination (last only)
==================================================================
   PASS D.1 last=2 returns success

==================================================================
E. get_workitem_notes — GPT scenario (first AND last both present)
   THIS IS THE BUG: GraphQL rejects both. resolve_pagination_direction
   should pick forward and succeed.
==================================================================
   FAIL E.1 first=3 + last=2 returns success (forward wins) -> Can only provide either `first` or `last`, not both
   PASS E.2 returned 0 notes (<=3, forward won)

==================================================================
F. get_workitem_notes — GPT scenario all four params
   (first + last + after + before — maximally eager GPT client)
==================================================================
   FAIL F.1 all four pagination params returns success (forward wins) -> Can only provide either `first` or `last`, not both, Can only provide either `first` or `last`, not both, Can only provide either `first` or `last`, not both

==================================================================
G. get_workitem_notes — cursor follow
==================================================================
   PASS G.1 page 2 via cursor returns success

==================================================================
H. agentic smoke: Claude lists MRs via MCP (end-to-end sanity)
==================================================================
   agent called: mcp__gdk__list_merge_requests
   PASS H.1 agent used list_merge_requests

==================================================================
>> Cleanup...
   closed MR !27
   closed issue #4
   deleted branch mcp-pagtest-1788266027
   revoked test PAT

==================================================================
RESULT: 10 passed, 2 failed

After (this branch)

>> Minting a local-GDK PAT (api+mcp)...
>> Preflight OK. Project: top-level-public/top-project-public (39)
>> Seeding test MR...
   Created MR !26
>> Seeding test issue (work item) with notes...
   Created issue #3
   Added 5 notes to issue

==================================================================
A. list_merge_requests — forward pagination (sanity check)
   (forward-only tool: schema only has first/after, GPT won't send last)
==================================================================
   PASS A.1 first=2 returns success
   PASS A.2 returned 0 MRs (<=2)

==================================================================
B. list_merge_requests — cursor follow (page 1 then page 2)
==================================================================
   PASS B.1 page 1 (first=1)
   PASS B.2 page 2 (after cursor)
   (B.3 skipped — project has only one MR, both pages return !)

==================================================================
C. get_workitem_notes — forward pagination (first only)
==================================================================
   PASS C.1 first=2 returns success
   PASS C.2 returned 2 notes (>=1)

==================================================================
D. get_workitem_notes — backward pagination (last only)
==================================================================
   PASS D.1 last=2 returns success

==================================================================
E. get_workitem_notes — GPT scenario (first AND last both present)
   THIS IS THE BUG: GraphQL rejects both. resolve_pagination_direction
   should pick forward and succeed.
==================================================================
   PASS E.1 first=3 + last=2 returns success (forward wins)
   PASS E.2 returned 3 notes (<=3, forward won)

==================================================================
F. get_workitem_notes — GPT scenario all four params
   (first + last + after + before — maximally eager GPT client)
==================================================================
   PASS F.1 all four pagination params returns success (forward wins)

==================================================================
G. get_workitem_notes — cursor follow
==================================================================
   PASS G.1 page 2 via cursor returns success

==================================================================
H. agentic smoke: Claude lists MRs via MCP (end-to-end sanity)
==================================================================
   agent called: mcp__gdk__list_merge_requests
   PASS H.1 agent used list_merge_requests

==================================================================
>> Cleanup...
   closed MR !26
   closed issue #3
   deleted branch mcp-pagtest-1788265935
   revoked test PAT

==================================================================
RESULT: 12 passed, 0 failed

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Terri Chu

Merge request reports

Loading
Loading