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_directionto theCursorPaginationconcern: forward wins when both are present, backward only whenlastarrives withoutfirst. 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 frominput_schema_paramsand its 6 call sites.
References
- #621520 (item 2)
- !251810 (merged) (introduced the
CursorPaginationconcern)
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: "" |
|
| this branch | first: 100, last: 100, after: "", before: "", url: "" |
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: "" |
|
| this branch | first: 100, last: 100, after: "", before: "", group_id: "", url: "" |
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 |
|
| this branch | (identical) |
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: "", ... |
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: "" |
|
| this branch | first: 5, last: 5, after: "", before: "", url: "" |
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: "" |
|
| this branch | first: 5, last: 100, after: "", before: "", url: "" |
Key observations
- GPT fills every optional param with type defaults (
first: 20or100,last: 1or100,after: "", etc.) regardless of schema description changes resolve_pagination_directionworks correctly — when bothfirstandlastare present, forward wins and GraphQL gets only one direction- Empty string params (
after: "",before: "",url: "") are harmless — they don't cause failures onceresolve_pagination_directionis active list_merge_requestsreturning 0 results is a GPT behavior issue (it defaultsscopetocreated_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 failedAfter (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 failedMR 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.