@@ -18,17 +18,25 @@ import (
1818
1919// userList represents a GitHub star list (UserList) surfaced through the tools.
2020type userList struct {
21- ID githubv4.ID `json:"id"`
22- Name string `json:"name"`
23- Description string `json:"description,omitempty"`
24- IsPrivate bool `json:"is_private"`
25- Items []userListItem `json:"items"`
21+ ID githubv4.ID `json:"id"`
22+ Name string `json:"name"`
23+ Description string `json:"description,omitempty"`
24+ IsPrivate bool `json:"is_private"`
25+ Items []userListItem `json:"items"`
26+ ItemsPageInfo * userListPageInfo `json:"itemsPageInfo,omitempty"`
2627}
2728
2829type userListItem struct {
2930 Repository string `json:"repository"`
3031}
3132
33+ type userListPageInfo struct {
34+ HasNextPage bool `json:"hasNextPage"`
35+ HasPreviousPage bool `json:"hasPreviousPage"`
36+ StartCursor string `json:"startCursor,omitempty"`
37+ EndCursor string `json:"endCursor,omitempty"`
38+ }
39+
3240type userListLookupQuery struct {
3341 Viewer struct {
3442 Lists struct {
@@ -53,12 +61,9 @@ type userListPageQuery struct {
5361 Description githubv4.String
5462 IsPrivate githubv4.Boolean
5563 }
56- PageInfo struct {
57- HasNextPage bool
58- EndCursor string
59- }
64+ PageInfo userListPageInfo
6065 TotalCount githubv4.Int
61- } `graphql:"lists(first: 100 , after: $after)"`
66+ } `graphql:"lists(first: $first , after: $after)"`
6267 }
6368}
6469
@@ -76,18 +81,12 @@ type userListPageWithItemsQuery struct {
7681 NameWithOwner githubv4.String
7782 } `graphql:"... on Repository"`
7883 }
79- PageInfo struct {
80- HasNextPage bool
81- EndCursor string
82- }
84+ PageInfo userListPageInfo
8385 } `graphql:"items(first: 100)"`
8486 }
85- PageInfo struct {
86- HasNextPage bool
87- EndCursor string
88- }
87+ PageInfo userListPageInfo
8988 TotalCount githubv4.Int
90- } `graphql:"lists(first: 100 , after: $after)"`
89+ } `graphql:"lists(first: $first , after: $after)"`
9190 }
9291}
9392
@@ -115,112 +114,49 @@ func getUserListID(ctx context.Context, client *githubv4.Client, name string) (g
115114 return "" , fmt .Errorf ("list '%s' not found" , name )
116115}
117116
118- // listUserLists returns the authenticated user's star lists, optionally
119- // including the repositories each list contains.
120- func listUserLists (ctx context.Context , client * githubv4.Client , includeItems bool ) ([]userList , int , error ) {
121- lists := make ([]userList , 0 )
122- totalCount := 0
123- var after * githubv4.String
124- for {
125- if includeItems {
126- var query userListPageWithItemsQuery
127- vars := map [string ]any {"after" : after }
128- if err := client .Query (ctx , & query , vars ); err != nil {
129- return nil , 0 , err
130- }
131- totalCount = int (query .Viewer .Lists .TotalCount )
132- for _ , node := range query .Viewer .Lists .Nodes {
133- items := make ([]userListItem , 0 , len (node .Items .Nodes ))
134- for _ , item := range node .Items .Nodes {
135- items = append (items , userListItem {Repository : string (item .Repository .NameWithOwner )})
136- }
137- if node .Items .PageInfo .HasNextPage {
138- cursor := githubv4 .String (node .Items .PageInfo .EndCursor )
139- remaining , err := listUserListItems (ctx , client , node .ID , & cursor )
140- if err != nil {
141- return nil , 0 , err
142- }
143- items = append (items , remaining ... )
144- }
145- lists = append (lists , userList {
146- ID : node .ID ,
147- Name : string (node .Name ),
148- Description : string (node .Description ),
149- IsPrivate : bool (node .IsPrivate ),
150- Items : items ,
151- })
152- }
153- if ! query .Viewer .Lists .PageInfo .HasNextPage {
154- break
155- }
156- cursor := githubv4 .String (query .Viewer .Lists .PageInfo .EndCursor )
157- after = & cursor
158- continue
159- }
160-
161- var query userListPageQuery
162- vars := map [string ]any {"after" : after }
117+ // listUserLists returns one bounded page of the authenticated user's star
118+ // lists. When includeItems is true, each list includes at most its first 100
119+ // repositories plus cursor metadata indicating whether more items exist.
120+ func listUserLists (ctx context.Context , client * githubv4.Client , includeItems bool , first githubv4.Int , after * githubv4.String ) ([]userList , int , userListPageInfo , error ) {
121+ vars := map [string ]any {"first" : first , "after" : after }
122+ if includeItems {
123+ var query userListPageWithItemsQuery
163124 if err := client .Query (ctx , & query , vars ); err != nil {
164- return nil , 0 , err
125+ return nil , 0 , userListPageInfo {}, err
165126 }
166- totalCount = int ( query .Viewer .Lists .TotalCount )
127+ lists := make ([] userList , 0 , len ( query .Viewer .Lists .Nodes ) )
167128 for _ , node := range query .Viewer .Lists .Nodes {
168- list := userList {
169- ID : node .ID ,
170- Name : string (node .Name ),
171- Description : string (node .Description ),
172- IsPrivate : bool (node .IsPrivate ),
173- }
174- lists = append (lists , list )
175- }
176- if ! query .Viewer .Lists .PageInfo .HasNextPage {
177- break
129+ items := make ([]userListItem , 0 , len (node .Items .Nodes ))
130+ for _ , item := range node .Items .Nodes {
131+ items = append (items , userListItem {Repository : string (item .Repository .NameWithOwner )})
132+ }
133+ itemsPageInfo := node .Items .PageInfo
134+ lists = append (lists , userList {
135+ ID : node .ID ,
136+ Name : string (node .Name ),
137+ Description : string (node .Description ),
138+ IsPrivate : bool (node .IsPrivate ),
139+ Items : items ,
140+ ItemsPageInfo : & itemsPageInfo ,
141+ })
178142 }
179- cursor := githubv4 .String (query .Viewer .Lists .PageInfo .EndCursor )
180- after = & cursor
143+ return lists , int (query .Viewer .Lists .TotalCount ), query .Viewer .Lists .PageInfo , nil
181144 }
182- return lists , totalCount , nil
183- }
184145
185- // listUserListItems returns the repositories held by a single list, following
186- // the items connection's cursor until every page has been consumed.
187- func listUserListItems (ctx context.Context , client * githubv4.Client , listID githubv4.ID , after * githubv4.String ) ([]userListItem , error ) {
188- items := make ([]userListItem , 0 )
189- for {
190- var query struct {
191- Node struct {
192- UserList struct {
193- Items struct {
194- Nodes []struct {
195- Repository struct {
196- NameWithOwner githubv4.String
197- } `graphql:"... on Repository"`
198- }
199- PageInfo struct {
200- HasNextPage bool
201- EndCursor string
202- }
203- } `graphql:"items(first: 100, after: $after)"`
204- } `graphql:"... on UserList"`
205- } `graphql:"node(id: $id)"`
206- }
207- vars := map [string ]any {
208- "id" : listID ,
209- "after" : after ,
210- }
211- if err := client .Query (ctx , & query , vars ); err != nil {
212- return nil , err
213- }
214- for _ , node := range query .Node .UserList .Items .Nodes {
215- items = append (items , userListItem {Repository : string (node .Repository .NameWithOwner )})
216- }
217- if ! query .Node .UserList .Items .PageInfo .HasNextPage {
218- break
219- }
220- cursor := githubv4 .String (query .Node .UserList .Items .PageInfo .EndCursor )
221- after = & cursor
146+ var query userListPageQuery
147+ if err := client .Query (ctx , & query , vars ); err != nil {
148+ return nil , 0 , userListPageInfo {}, err
222149 }
223- return items , nil
150+ lists := make ([]userList , 0 , len (query .Viewer .Lists .Nodes ))
151+ for _ , node := range query .Viewer .Lists .Nodes {
152+ lists = append (lists , userList {
153+ ID : node .ID ,
154+ Name : string (node .Name ),
155+ Description : string (node .Description ),
156+ IsPrivate : bool (node .IsPrivate ),
157+ })
158+ }
159+ return lists , int (query .Viewer .Lists .TotalCount ), query .Viewer .Lists .PageInfo , nil
224160}
225161
226162// repoInList reports whether the repository identified by repoID belongs to the
@@ -494,35 +430,50 @@ func ListUserLists(t translations.TranslationHelperFunc) inventory.ServerTool {
494430 Title : t ("TOOL_LIST_USER_LISTS_USER_TITLE" , "List star lists" ),
495431 ReadOnlyHint : true ,
496432 },
497- InputSchema : & jsonschema.Schema {
433+ InputSchema : WithCursorPagination ( & jsonschema.Schema {
498434 Type : "object" ,
499435 Properties : map [string ]* jsonschema.Schema {
500436 "include_items" : {
501437 Type : "boolean" ,
502438 Description : "Whether to include the repositories in each list." ,
503439 },
504440 },
505- },
441+ }) ,
506442 },
507443 userListReadScopeAccess (),
508444 func (ctx context.Context , deps ToolDependencies , _ * mcp.CallToolRequest , args map [string ]any ) (* mcp.CallToolResult , any , error ) {
509445 includeItems , err := OptionalParam [bool ](args , "include_items" )
510446 if err != nil {
511447 return utils .NewToolResultError (err .Error ()), nil , nil
512448 }
449+ pagination , err := OptionalCursorPaginationParams (args )
450+ if err != nil {
451+ return utils .NewToolResultError (err .Error ()), nil , nil
452+ }
453+ paginationParams , err := pagination .ToGraphQLParams ()
454+ if err != nil {
455+ return utils .NewToolResultError (err .Error ()), nil , nil
456+ }
457+ first := githubv4 .Int (* paginationParams .First )
458+ var after * githubv4.String
459+ if paginationParams .After != nil {
460+ cursor := githubv4 .String (* paginationParams .After )
461+ after = & cursor
462+ }
513463
514464 client , err := deps .GetGQLClient (ctx )
515465 if err != nil {
516466 return nil , nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
517467 }
518468
519- lists , totalCount , err := listUserLists (ctx , client , includeItems )
469+ lists , totalCount , pageInfo , err := listUserLists (ctx , client , includeItems , first , after )
520470 if err != nil {
521471 return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to list user lists" , err ), nil , nil
522472 }
523473
524474 response := map [string ]any {
525475 "lists" : lists ,
476+ "pageInfo" : pageInfo ,
526477 "totalCount" : totalCount ,
527478 }
528479 out , err := json .Marshal (response )
0 commit comments