Skip to content

Commit c462cfc

Browse files
committed
fix(pull_request_read): expose after cursor parameter in input schema
The `pull_request_read` tool description tells clients that `get_review_comments` uses cursor-based pagination (`perPage`, `after`), and the handler does plumb `after` through to the GraphQL query, but the input schema only declared `page` and `perPage` (via `WithPagination`). Because `after` was not advertised in `inputSchema`, MCP clients had no way to request it, leaving cursor pagination effectively broken: `perPage: 1` returned only the first thread with no way to advance, and `page` was silently ignored by the GraphQL path. This change adds `after` to the schema (string, optional) with a description making clear it only applies to `get_review_comments`. All other methods continue to ignore it. No handler behavior is changed. - Add `after` schema property after `WithPagination` in `PullRequestRead` - Regenerate `__toolsnaps__/pull_request_read.snap` and update README - Add a regression test asserting `after` is in the schema and a new table-driven case verifying the cursor is forwarded to the GraphQL query Fixes #2122 (for the `get_review_comments` pagination part). The remaining concerns in #2122 about unbounded response sizes for `get`, `get_diff`, and `get_reviews` are deferred to follow-up design.
1 parent 2dab994 commit c462cfc

4 files changed

Lines changed: 65 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ The following sets of tools are available:
10931093

10941094
- **pull_request_read** - Get details for a single pull request
10951095
- **Required OAuth Scopes**: `repo`
1096+
- `after`: Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page. (string, optional)
10961097
- `method`: Action to specify what pull request data needs to be retrieved from GitHub.
10971098
Possible options:
10981099
1. get - Get details of a specific pull request.

pkg/github/__toolsnaps__/pull_request_read.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
"description": "Get information on a specific pull request in GitHub repository.",
77
"inputSchema": {
88
"properties": {
9+
"after": {
10+
"description": "Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page.",
11+
"type": "string"
12+
},
913
"method": {
1014
"description": "Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get combined commit status of a head commit in a pull request.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns threads with metadata (isResolved, isOutdated, isCollapsed) and their associated comments. Use cursor-based pagination (perPage, after) to control results.\n 6. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method.\n 7. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n 8. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.\n",
1115
"enum": [

pkg/github/pullrequests.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ Possible options:
5858
Required: []string{"method", "owner", "repo", "pullNumber"},
5959
}
6060
WithPagination(schema)
61+
// get_review_comments uses GraphQL cursor-based pagination and accepts the
62+
// `after` cursor. Other methods rely on the `page`/`perPage` parameters
63+
// added by WithPagination and ignore `after`.
64+
schema.Properties["after"] = &jsonschema.Schema{
65+
Type: "string",
66+
Description: "Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page.",
67+
}
6168

6269
return NewTool(
6370
ToolsetMetadataPullRequests,

pkg/github/pullrequests_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,11 @@ func Test_GetPullRequestComments(t *testing.T) {
16871687
assert.Contains(t, schema.Properties, "owner")
16881688
assert.Contains(t, schema.Properties, "repo")
16891689
assert.Contains(t, schema.Properties, "pullNumber")
1690+
// `after` is required for cursor-based pagination on get_review_comments
1691+
// to be reachable from MCP clients; without it in the schema, callers
1692+
// cannot advance past the first page (issue #2122).
1693+
assert.Contains(t, schema.Properties, "after")
1694+
assert.Equal(t, "string", schema.Properties["after"].Type)
16901695
assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})
16911696

16921697
tests := []struct {
@@ -1804,6 +1809,54 @@ func Test_GetPullRequestComments(t *testing.T) {
18041809
assert.Equal(t, 1, result.TotalCount)
18051810
},
18061811
},
1812+
{
1813+
name: "after cursor is forwarded to GraphQL query",
1814+
gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
1815+
githubv4mock.NewQueryMatcher(
1816+
reviewThreadsQuery{},
1817+
map[string]any{
1818+
"owner": githubv4.String("owner"),
1819+
"repo": githubv4.String("repo"),
1820+
"prNum": githubv4.Int(42),
1821+
"first": githubv4.Int(30),
1822+
"commentsPerThread": githubv4.Int(100),
1823+
"after": githubv4.String("cursor-page-2"),
1824+
},
1825+
githubv4mock.DataResponse(map[string]any{
1826+
"repository": map[string]any{
1827+
"pullRequest": map[string]any{
1828+
"reviewThreads": map[string]any{
1829+
"nodes": []map[string]any{},
1830+
"pageInfo": map[string]any{
1831+
"hasNextPage": false,
1832+
"hasPreviousPage": true,
1833+
"startCursor": "cursor3",
1834+
"endCursor": "cursor4",
1835+
},
1836+
"totalCount": 5,
1837+
},
1838+
},
1839+
},
1840+
}),
1841+
),
1842+
),
1843+
requestArgs: map[string]any{
1844+
"method": "get_review_comments",
1845+
"owner": "owner",
1846+
"repo": "repo",
1847+
"pullNumber": float64(42),
1848+
"after": "cursor-page-2",
1849+
},
1850+
expectError: false,
1851+
validateResult: func(t *testing.T, textContent string) {
1852+
var result MinimalReviewThreadsResponse
1853+
err := json.Unmarshal([]byte(textContent), &result)
1854+
require.NoError(t, err)
1855+
assert.Len(t, result.ReviewThreads, 0)
1856+
assert.Equal(t, true, result.PageInfo.HasPreviousPage)
1857+
assert.Equal(t, "cursor4", result.PageInfo.EndCursor)
1858+
},
1859+
},
18071860
{
18081861
name: "review threads fetch fails",
18091862
gqlHTTPClient: githubv4mock.NewMockedHTTPClient(

0 commit comments

Comments
 (0)