@@ -18,6 +18,34 @@ import (
18
18
"github.com/shurcooL/githubv4"
19
19
)
20
20
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
+
21
49
// GetIssue creates a tool to get details of a specific issue in a GitHub repository.
22
50
func GetIssue (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
23
51
return mcp .NewTool ("get_issue" ,
@@ -726,7 +754,7 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
726
754
// ListIssues creates a tool to list and filter repository issues
727
755
func ListIssues (getGQLClient GetGQLClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
728
756
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. " )),
730
758
mcp .WithToolAnnotation (mcp.ToolAnnotation {
731
759
Title : t ("TOOL_LIST_ISSUES_USER_TITLE" , "List issues" ),
732
760
ReadOnlyHint : ToBoolPtr (true ),
@@ -740,6 +768,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
740
768
mcp .Description ("Repository name" ),
741
769
),
742
770
mcp .WithString ("state" ,
771
+ mcp .Required (),
743
772
mcp .Description ("Filter by state" ),
744
773
mcp .Enum ("OPEN" , "CLOSED" ),
745
774
),
@@ -752,17 +781,17 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
752
781
),
753
782
),
754
783
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." ),
756
785
mcp .Enum ("CREATED_AT" , "UPDATED_AT" ),
757
786
),
758
787
mcp .WithString ("direction" ,
759
- mcp .Description ("Order direction." ),
788
+ mcp .Description ("Order direction. If provided, the 'orderBy' also needs to be provided. " ),
760
789
mcp .Enum ("ASC" , "DESC" ),
761
790
),
762
791
mcp .WithString ("since" ,
763
792
mcp .Description ("Filter by date (ISO 8601 timestamp)" ),
764
793
),
765
- WithPagination (),
794
+ WithCursorPagination (),
766
795
),
767
796
func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
768
797
owner , err := RequiredParam [string ](request , "owner" )
@@ -775,16 +804,13 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
775
804
}
776
805
777
806
// Set optional parameters if provided
778
- state , err := OptionalParam [string ](request , "state" )
807
+ state , err := RequiredParam [string ](request , "state" )
779
808
if err != nil {
780
809
return mcp .NewToolResultError (err .Error ()), nil
781
810
}
782
- if state == "" {
783
- state = "OPEN" // Default to OPEN if not provided
784
- }
785
811
786
812
// Get labels
787
- // labels, err := OptionalStringArrayParam(request, "labels")
813
+ labels , err := OptionalStringArrayParam (request , "labels" )
788
814
if err != nil {
789
815
return mcp .NewToolResultError (err .Error ()), nil
790
816
}
@@ -793,6 +819,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
793
819
if err != nil {
794
820
return mcp .NewToolResultError (err .Error ()), nil
795
821
}
822
+ //If orderBy is empty, default to CREATED_AT
796
823
if orderBy == "" {
797
824
orderBy = "CREATED_AT"
798
825
}
@@ -801,6 +828,7 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
801
828
if err != nil {
802
829
return mcp .NewToolResultError (err .Error ()), nil
803
830
}
831
+ //If direction is empty, default to DESC
804
832
if direction == "" {
805
833
direction = "DESC"
806
834
}
@@ -809,64 +837,99 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
809
837
if err != nil {
810
838
return mcp .NewToolResultError (err .Error ()), nil
811
839
}
840
+
841
+ var sinceTime time.Time
812
842
if since != "" {
813
- //timestamp , err : = parseISOTimestamp(since)
843
+ sinceTime , err = parseISOTimestamp (since )
814
844
if err != nil {
815
845
return mcp .NewToolResultError (fmt .Sprintf ("failed to list issues: %s" , err .Error ())), nil
816
846
}
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
818
857
}
819
858
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
823
862
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
+ }
827
878
828
879
client , err := getGQLClient (ctx )
829
880
if err != nil {
830
881
return mcp .NewToolResultError (fmt .Sprintf ("failed to get GitHub GQL client: %v" , err )), nil
831
882
}
832
883
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
- }
853
884
vars := map [string ]interface {}{
854
885
"owner" : githubv4 .String (owner ),
855
886
"repo" : githubv4 .String (repo ),
856
- "first" : githubv4 .Int (100 ),
857
887
"states" : []githubv4.IssueState {githubv4 .IssueState (state )},
858
888
"orderBy" : githubv4 .IssueOrderField (orderBy ),
859
889
"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 )
860
897
}
861
- if err := client .Query (ctx , & q , vars ); err != nil {
898
+
899
+ if err := client .Query (ctx , & ListIssuesQuery , vars ); err != nil {
862
900
return mcp .NewToolResultError (err .Error ()), nil
863
901
}
864
902
903
+ //We must filter based on labels after fetching all issues
865
904
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
868
907
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
+ }
870
933
}
871
934
872
935
issues = append (issues , map [string ]interface {}{
@@ -875,15 +938,21 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
875
938
"body" : string (issue .Body ),
876
939
"author" : string (issue .Author .Login ),
877
940
"createdAt" : issue .CreatedAt .Time ,
878
- "labels" : labels ,
941
+ "labels" : issueLabels ,
879
942
})
880
943
}
881
944
882
945
// Create response with issues
883
946
response := map [string ]interface {}{
884
947
"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 ,
885
955
}
886
-
887
956
out , err := json .Marshal (response )
888
957
if err != nil {
889
958
return nil , fmt .Errorf ("failed to marshal issues: %w" , err )
0 commit comments