@@ -18,6 +18,34 @@ import (
1818 "github.com/shurcooL/githubv4"
1919)
2020
21+ var ListIssuesQuery struct {
22+ Repository struct {
23+ Issues struct {
24+ Nodes []struct {
25+ Number githubv4.Int
26+ Title githubv4.String
27+ Body githubv4.String
28+ Author struct {
29+ Login githubv4.String
30+ }
31+ CreatedAt githubv4.DateTime
32+ Labels struct {
33+ Nodes []struct {
34+ Name githubv4.String
35+ }
36+ } `graphql:"labels(first: 10)"`
37+ }
38+ PageInfo struct {
39+ HasNextPage githubv4.Boolean
40+ HasPreviousPage githubv4.Boolean
41+ StartCursor githubv4.String
42+ EndCursor githubv4.String
43+ }
44+ TotalCount int
45+ } `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
46+ } `graphql:"repository(owner: $owner, name: $repo)"`
47+ }
48+
2149// GetIssue creates a tool to get details of a specific issue in a GitHub repository.
2250func GetIssue (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
2351 return mcp .NewTool ("get_issue" ,
@@ -726,7 +754,7 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
726754// ListIssues creates a tool to list and filter repository issues
727755func ListIssues (getGQLClient GetGQLClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
728756 return mcp .NewTool ("list_issues" ,
729- mcp .WithDescription (t ("TOOL_LIST_ISSUES_DESCRIPTION" , "List issues in a GitHub repository." )),
757+ mcp .WithDescription (t ("TOOL_LIST_ISSUES_DESCRIPTION" , "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter. " )),
730758 mcp .WithToolAnnotation (mcp.ToolAnnotation {
731759 Title : t ("TOOL_LIST_ISSUES_USER_TITLE" , "List issues" ),
732760 ReadOnlyHint : ToBoolPtr (true ),
@@ -740,6 +768,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
740768 mcp .Description ("Repository name" ),
741769 ),
742770 mcp .WithString ("state" ,
771+ mcp .Required (),
743772 mcp .Description ("Filter by state" ),
744773 mcp .Enum ("OPEN" , "CLOSED" ),
745774 ),
@@ -752,17 +781,17 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
752781 ),
753782 ),
754783 mcp .WithString ("orderBy" ,
755- mcp .Description ("Order discussions by field. If provided, the 'direction' also needs to be provided." ),
784+ mcp .Description ("Order issues by field. If provided, the 'direction' also needs to be provided." ),
756785 mcp .Enum ("CREATED_AT" , "UPDATED_AT" ),
757786 ),
758787 mcp .WithString ("direction" ,
759- mcp .Description ("Order direction." ),
788+ mcp .Description ("Order direction. If provided, the 'orderBy' also needs to be provided. " ),
760789 mcp .Enum ("ASC" , "DESC" ),
761790 ),
762791 mcp .WithString ("since" ,
763792 mcp .Description ("Filter by date (ISO 8601 timestamp)" ),
764793 ),
765- WithPagination (),
794+ WithCursorPagination (),
766795 ),
767796 func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
768797 owner , err := RequiredParam [string ](request , "owner" )
@@ -775,16 +804,13 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
775804 }
776805
777806 // Set optional parameters if provided
778- state , err := OptionalParam [string ](request , "state" )
807+ state , err := RequiredParam [string ](request , "state" )
779808 if err != nil {
780809 return mcp .NewToolResultError (err .Error ()), nil
781810 }
782- if state == "" {
783- state = "OPEN" // Default to OPEN if not provided
784- }
785811
786812 // Get labels
787- // labels, err := OptionalStringArrayParam(request, "labels")
813+ labels , err := OptionalStringArrayParam (request , "labels" )
788814 if err != nil {
789815 return mcp .NewToolResultError (err .Error ()), nil
790816 }
@@ -793,6 +819,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
793819 if err != nil {
794820 return mcp .NewToolResultError (err .Error ()), nil
795821 }
822+ //If orderBy is empty, default to CREATED_AT
796823 if orderBy == "" {
797824 orderBy = "CREATED_AT"
798825 }
@@ -801,6 +828,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
801828 if err != nil {
802829 return mcp .NewToolResultError (err .Error ()), nil
803830 }
831+ //If direction is empty, default to DESC
804832 if direction == "" {
805833 direction = "DESC"
806834 }
@@ -809,64 +837,99 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
809837 if err != nil {
810838 return mcp .NewToolResultError (err .Error ()), nil
811839 }
840+
841+ var sinceTime time.Time
812842 if since != "" {
813- //timestamp , err : = parseISOTimestamp(since)
843+ sinceTime , err = parseISOTimestamp (since )
814844 if err != nil {
815845 return mcp .NewToolResultError (fmt .Sprintf ("failed to list issues: %s" , err .Error ())), nil
816846 }
817- //since = timestamp
847+ }
848+ // Get pagination parameters and convert to GraphQL format
849+ pagination , err := OptionalCursorPaginationParams (request )
850+ if err != nil {
851+ return nil , err
852+ }
853+
854+ // Check if someone tried to use page-based pagination instead of cursor-based
855+ if _ , pageProvided := request .GetArguments ()["page" ]; pageProvided {
856+ return mcp .NewToolResultError ("This tool uses cursor-based pagination. Use the 'after' parameter with the 'endCursor' value from the previous response instead of 'page'." ), nil
818857 }
819858
820- //if page, ok := request.GetArguments()["page"].(float64); ok {
821- //listOptions.Page = int(page)
822- //}
859+ // Check if pagination parameters were explicitly provided
860+ _ , perPageProvided := request . GetArguments ()[ "perPage" ]
861+ paginationExplicit := perPageProvided
823862
824- //if perPage, ok := request.GetArguments()["perPage"].(float64); ok {
825- //.PerPage = int(perPage)
826- //}
863+ paginationParams , err := pagination .ToGraphQLParams ()
864+ if err != nil {
865+ return nil , err
866+ }
867+
868+ if paginationParams .After == nil {
869+ defaultAfter := string ("" )
870+ paginationParams .After = & defaultAfter
871+ }
872+
873+ // Use default of 30 if pagination was not explicitly provided
874+ if ! paginationExplicit {
875+ defaultFirst := int32 (DefaultGraphQLPageSize )
876+ paginationParams .First = & defaultFirst
877+ }
827878
828879 client , err := getGQLClient (ctx )
829880 if err != nil {
830881 return mcp .NewToolResultError (fmt .Sprintf ("failed to get GitHub GQL client: %v" , err )), nil
831882 }
832883
833- var q struct {
834- Repository struct {
835- Issues struct {
836- Nodes []struct {
837- Number githubv4.Int
838- Title githubv4.String
839- Body githubv4.String
840- Author struct {
841- Login githubv4.String
842- }
843- CreatedAt githubv4.DateTime
844- Labels struct {
845- Nodes []struct {
846- Name githubv4.String
847- }
848- } `graphql:"labels(first: 10)"`
849- }
850- } `graphql:"issues(first: $first, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
851- } `graphql:"repository(owner: $owner, name: $repo)"`
852- }
853884 vars := map [string ]interface {}{
854885 "owner" : githubv4 .String (owner ),
855886 "repo" : githubv4 .String (repo ),
856- "first" : githubv4 .Int (100 ),
857887 "states" : []githubv4.IssueState {githubv4 .IssueState (state )},
858888 "orderBy" : githubv4 .IssueOrderField (orderBy ),
859889 "direction" : githubv4 .OrderDirection (direction ),
890+ "first" : githubv4 .Int (* paginationParams .First ),
891+ }
892+
893+ if paginationParams .After != nil {
894+ vars ["after" ] = githubv4 .String (* paginationParams .After )
895+ } else {
896+ vars ["after" ] = (* githubv4 .String )(nil )
860897 }
861- if err := client .Query (ctx , & q , vars ); err != nil {
898+
899+ if err := client .Query (ctx , & ListIssuesQuery , vars ); err != nil {
862900 return mcp .NewToolResultError (err .Error ()), nil
863901 }
864902
903+ //We must filter based on labels after fetching all issues
865904 var issues []map [string ]interface {}
866- for _ , issue := range q .Repository .Issues .Nodes {
867- var labels []string
905+ for _ , issue := range ListIssuesQuery .Repository .Issues .Nodes {
906+ var issueLabels []string
868907 for _ , label := range issue .Labels .Nodes {
869- labels = append (labels , string (label .Name ))
908+ issueLabels = append (issueLabels , string (label .Name ))
909+ }
910+
911+ // Filter by since date if specified
912+ if ! sinceTime .IsZero () && issue .CreatedAt .Time .Before (sinceTime ) {
913+ continue // Skip issues created before the since date
914+ }
915+
916+ // Filter by labels if specified
917+ if len (labels ) > 0 {
918+ hasMatchingLabel := false
919+ for _ , requestedLabel := range labels {
920+ for _ , issueLabel := range issueLabels {
921+ if strings .EqualFold (requestedLabel , issueLabel ) {
922+ hasMatchingLabel = true
923+ break
924+ }
925+ }
926+ if hasMatchingLabel {
927+ break
928+ }
929+ }
930+ if ! hasMatchingLabel {
931+ continue // Skip this issue as it doesn't match any requested labels
932+ }
870933 }
871934
872935 issues = append (issues , map [string ]interface {}{
@@ -875,15 +938,21 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
875938 "body" : string (issue .Body ),
876939 "author" : string (issue .Author .Login ),
877940 "createdAt" : issue .CreatedAt .Time ,
878- "labels" : labels ,
941+ "labels" : issueLabels ,
879942 })
880943 }
881944
882945 // Create response with issues
883946 response := map [string ]interface {}{
884947 "issues" : issues ,
948+ "pageInfo" : map [string ]interface {}{
949+ "hasNextPage" : ListIssuesQuery .Repository .Issues .PageInfo .HasNextPage ,
950+ "hasPreviousPage" : ListIssuesQuery .Repository .Issues .PageInfo .HasPreviousPage ,
951+ "startCursor" : string (ListIssuesQuery .Repository .Issues .PageInfo .StartCursor ),
952+ "endCursor" : string (ListIssuesQuery .Repository .Issues .PageInfo .EndCursor ),
953+ },
954+ "totalCount" : ListIssuesQuery .Repository .Issues .TotalCount ,
885955 }
886-
887956 out , err := json .Marshal (response )
888957 if err != nil {
889958 return nil , fmt .Errorf ("failed to marshal issues: %w" , err )
0 commit comments