@@ -6,14 +6,11 @@ import (
66 "fmt"
77 "io"
88 "net/http"
9- "net/url"
10- "reflect"
119 "strings"
1210
1311 ghErrors "github.com/github/github-mcp-server/pkg/errors"
1412 "github.com/github/github-mcp-server/pkg/translations"
1513 "github.com/google/go-github/v79/github"
16- "github.com/google/go-querystring/query"
1714 "github.com/mark3labs/mcp-go/mcp"
1815 "github.com/mark3labs/mcp-go/server"
1916)
@@ -256,30 +253,19 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
256253 return mcp .NewToolResultError (err .Error ()), nil
257254 }
258255
256+ var resp * github.Response
257+ var projectFields []* github.ProjectV2Field
258+
259259 opts := & github.ListProjectsOptions {
260260 ListProjectsPaginationOptions : pagination ,
261261 }
262262
263- var url string
264263 if ownerType == "org" {
265- url = fmt . Sprintf ( "orgs/%s/projectsV2/%d/fields" , owner , projectNumber )
264+ projectFields , resp , err = client . Projects . ListOrganizationProjectFields ( ctx , owner , projectNumber , opts )
266265 } else {
267- url = fmt .Sprintf ("users/%s/projectsV2/%d/fields" , owner , projectNumber )
268- }
269-
270- url , err = addOptions (url , opts )
271- if err != nil {
272- return mcp .NewToolResultError (err .Error ()), nil
266+ projectFields , resp , err = client .Projects .ListUserProjectFields (ctx , owner , projectNumber , opts )
273267 }
274268
275- httpRequest , err := client .NewRequest ("GET" , url , nil )
276- if err != nil {
277- return nil , fmt .Errorf ("failed to create request: %w" , err )
278- }
279-
280- var projectFields []projectV2Field
281- resp , err := client .Do (ctx , httpRequest , & projectFields )
282-
283269 if err != nil {
284270 return ghErrors .NewGitHubAPIErrorResponse (ctx ,
285271 "failed to list project fields" ,
@@ -452,7 +438,7 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
452438 }
453439
454440 var resp * github.Response
455- var projectItems []projectV2Item
441+ var projectItems []* github. ProjectV2Item
456442 var queryPtr * string
457443
458444 if queryStr != "" {
@@ -467,25 +453,12 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
467453 },
468454 }
469455
470- var url string
471456 if ownerType == "org" {
472- url = fmt . Sprintf ( "orgs/%s/projectsV2/%d/items" , owner , projectNumber )
457+ projectItems , resp , err = client . Projects . ListOrganizationProjectItems ( ctx , owner , projectNumber , opts )
473458 } else {
474- url = fmt . Sprintf ( "users/%s/projectsV2/%d/items" , owner , projectNumber )
459+ projectItems , resp , err = client . Projects . ListUserProjectItems ( ctx , owner , projectNumber , opts )
475460 }
476461
477- url , err = addOptions (url , opts )
478- if err != nil {
479- return mcp .NewToolResultError (err .Error ()), nil
480- }
481-
482- httpRequest , err := client .NewRequest ("GET" , url , nil )
483- if err != nil {
484- return nil , fmt .Errorf ("failed to create request: %w" , err )
485- }
486-
487- resp , err = client .Do (ctx , httpRequest , & projectItems )
488-
489462 if err != nil {
490463 return ghErrors .NewGitHubAPIErrorResponse (ctx ,
491464 ProjectListFailedError ,
@@ -566,32 +539,22 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
566539 return mcp .NewToolResultError (err .Error ()), nil
567540 }
568541
569- var url string
570- if ownerType == "org" {
571- url = fmt .Sprintf ("orgs/%s/projectsV2/%d/items/%d" , owner , projectNumber , itemID )
572- } else {
573- url = fmt .Sprintf ("users/%s/projectsV2/%d/items/%d" , owner , projectNumber , itemID )
574- }
575-
576- opts := fieldSelectionOptions {}
542+ var resp * github.Response
543+ var projectItem * github.ProjectV2Item
544+ var opts * github.GetProjectItemOptions
577545
578546 if len (fields ) > 0 {
579- opts .Fields = fields
580- }
581-
582- url , err = addOptions (url , opts )
583- if err != nil {
584- return mcp .NewToolResultError (err .Error ()), nil
547+ opts = & github.GetProjectItemOptions {
548+ Fields : fields ,
549+ }
585550 }
586551
587- projectItem := projectV2Item {}
588-
589- httpRequest , err := client .NewRequest ("GET" , url , nil )
590- if err != nil {
591- return nil , fmt .Errorf ("failed to create request: %w" , err )
552+ if ownerType == "org" {
553+ projectItem , resp , err = client .Projects .GetOrganizationProjectItem (ctx , owner , projectNumber , itemID , opts )
554+ } else {
555+ projectItem , resp , err = client .Projects .GetUserProjectItem (ctx , owner , projectNumber , itemID , opts )
592556 }
593557
594- resp , err := client .Do (ctx , httpRequest , & projectItem )
595558 if err != nil {
596559 return ghErrors .NewGitHubAPIErrorResponse (ctx ,
597560 "failed to get project item" ,
@@ -748,7 +711,7 @@ func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
748711 if err != nil {
749712 return mcp .NewToolResultError (err .Error ()), nil
750713 }
751- itemID , err := RequiredInt (req , "item_id" )
714+ itemID , err := RequiredBigInt (req , "item_id" )
752715 if err != nil {
753716 return mcp .NewToolResultError (err .Error ()), nil
754717 }
@@ -773,21 +736,15 @@ func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
773736 return mcp .NewToolResultError (err .Error ()), nil
774737 }
775738
776- var projectsURL string
739+ var resp * github.Response
740+ var updatedItem * github.ProjectV2Item
741+
777742 if ownerType == "org" {
778- projectsURL = fmt . Sprintf ( "orgs/%s/projectsV2/%d/items/%d" , owner , projectNumber , itemID )
743+ updatedItem , resp , err = client . Projects . UpdateOrganizationProjectItem ( ctx , owner , projectNumber , itemID , updatePayload )
779744 } else {
780- projectsURL = fmt .Sprintf ("users/%s/projectsV2/%d/items/%d" , owner , projectNumber , itemID )
781- }
782- httpRequest , err := client .NewRequest ("PATCH" , projectsURL , updateProjectItemPayload {
783- Fields : []updateProjectItem {* updatePayload },
784- })
785- if err != nil {
786- return nil , fmt .Errorf ("failed to create request: %w" , err )
745+ updatedItem , resp , err = client .Projects .UpdateUserProjectItem (ctx , owner , projectNumber , itemID , updatePayload )
787746 }
788- updatedItem := projectV2Item {}
789747
790- resp , err := client .Do (ctx , httpRequest , & updatedItem )
791748 if err != nil {
792749 return ghErrors .NewGitHubAPIErrorResponse (ctx ,
793750 ProjectUpdateFailedError ,
@@ -886,76 +843,13 @@ func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
886843 }
887844}
888845
889- type fieldSelectionOptions struct {
890- // Specific list of field IDs to include in the response. If not provided, only the title field is included.
891- // The comma tag encodes the slice as comma-separated values: fields=102589,985201,169875
892- Fields []int64 `url:"fields,omitempty,comma"`
893- }
894-
895- type updateProjectItemPayload struct {
896- Fields []updateProjectItem `json:"fields"`
897- }
898-
899- type updateProjectItem struct {
900- ID int `json:"id"`
901- Value any `json:"value"`
902- }
903-
904- type projectV2ItemFieldValue struct {
905- ID * int64 `json:"id,omitempty"`
906- Name string `json:"name,omitempty"`
907- DataType string `json:"data_type,omitempty"`
908- Value any `json:"value,omitempty"`
909- }
910-
911- type projectV2Item struct {
912- ArchivedAt * github.Timestamp `json:"archived_at,omitempty"`
913- Content * projectV2ItemContent `json:"content,omitempty"`
914- ContentType * string `json:"content_type,omitempty"`
915- CreatedAt * github.Timestamp `json:"created_at,omitempty"`
916- Creator * github.User `json:"creator,omitempty"`
917- Description * string `json:"description,omitempty"`
918- Fields []* projectV2ItemFieldValue `json:"fields,omitempty"`
919- ID * int64 `json:"id,omitempty"`
920- ItemURL * string `json:"item_url,omitempty"`
921- NodeID * string `json:"node_id,omitempty"`
922- ProjectURL * string `json:"project_url,omitempty"`
923- Title * string `json:"title,omitempty"`
924- UpdatedAt * github.Timestamp `json:"updated_at,omitempty"`
925- }
926-
927- type projectV2ItemContent struct {
928- Body * string `json:"body,omitempty"`
929- ClosedAt * github.Timestamp `json:"closed_at,omitempty"`
930- CreatedAt * github.Timestamp `json:"created_at,omitempty"`
931- ID * int64 `json:"id,omitempty"`
932- Number * int `json:"number,omitempty"`
933- State * string `json:"state,omitempty"`
934- StateReason * string `json:"stateReason,omitempty"`
935- Title * string `json:"title,omitempty"`
936- UpdatedAt * github.Timestamp `json:"updated_at,omitempty"`
937- URL * string `json:"url,omitempty"`
938- }
939-
940846type pageInfo struct {
941847 HasNextPage bool `json:"hasNextPage"`
942848 HasPreviousPage bool `json:"hasPreviousPage"`
943849 NextCursor string `json:"nextCursor,omitempty"`
944850 PrevCursor string `json:"prevCursor,omitempty"`
945851}
946852
947- type projectV2Field struct {
948- ID * int64 `json:"id,omitempty"`
949- NodeID * string `json:"node_id,omitempty"`
950- Name * string `json:"name,omitempty"`
951- DataType * string `json:"data_type,omitempty"`
952- ProjectURL * string `json:"project_url,omitempty"`
953- Options []any `json:"options,omitempty"`
954- Configuration any `json:"configuration,omitempty"`
955- CreatedAt * github.Timestamp `json:"created_at,omitempty"`
956- UpdatedAt * github.Timestamp `json:"updated_at,omitempty"`
957- }
958-
959853func toNewProjectType (projType string ) string {
960854 switch strings .ToLower (projType ) {
961855 case "issue" :
@@ -967,7 +861,27 @@ func toNewProjectType(projType string) string {
967861 }
968862}
969863
970- func buildUpdateProjectItem (input map [string ]any ) (* updateProjectItem , error ) {
864+ // validateAndConvertToInt64 ensures the value is a number and converts it to int64.
865+ func validateAndConvertToInt64 (value any ) (int64 , error ) {
866+ switch v := value .(type ) {
867+ case float64 :
868+ // Validate that the float64 can be safely converted to int64
869+ intVal := int64 (v )
870+ if float64 (intVal ) != v {
871+ return 0 , fmt .Errorf ("value must be a valid integer (got %v)" , v )
872+ }
873+ return intVal , nil
874+ case int64 :
875+ return v , nil
876+ case int :
877+ return int64 (v ), nil
878+ default :
879+ return 0 , fmt .Errorf ("value must be a number (got %T)" , v )
880+ }
881+ }
882+
883+ // buildUpdateProjectItem constructs UpdateProjectItemOptions from the input map.
884+ func buildUpdateProjectItem (input map [string ]any ) (* github.UpdateProjectItemOptions , error ) {
971885 if input == nil {
972886 return nil , fmt .Errorf ("updated_field must be an object" )
973887 }
@@ -977,16 +891,22 @@ func buildUpdateProjectItem(input map[string]any) (*updateProjectItem, error) {
977891 return nil , fmt .Errorf ("updated_field.id is required" )
978892 }
979893
980- idFieldAsFloat64 , ok := idField .( float64 ) // JSON numbers are float64
981- if ! ok {
982- return nil , fmt .Errorf ("updated_field.id must be a number" )
894+ fieldID , err := validateAndConvertToInt64 ( idField )
895+ if err != nil {
896+ return nil , fmt .Errorf ("updated_field.id: %w" , err )
983897 }
984898
985899 valueField , ok := input ["value" ]
986900 if ! ok {
987901 return nil , fmt .Errorf ("updated_field.value is required" )
988902 }
989- payload := & updateProjectItem {ID : int (idFieldAsFloat64 ), Value : valueField }
903+
904+ payload := & github.UpdateProjectItemOptions {
905+ Fields : []* github.UpdateProjectV2Field {{
906+ ID : fieldID ,
907+ Value : valueField ,
908+ }},
909+ }
990910
991911 return payload , nil
992912}
@@ -1034,35 +954,3 @@ func extractPaginationOptions(request mcp.CallToolRequest) (github.ListProjectsP
1034954
1035955 return opts , nil
1036956}
1037-
1038- // addOptions adds the parameters in opts as URL query parameters to s. opts
1039- // must be a struct whose fields may contain "url" tags.
1040- func addOptions (s string , opts any ) (string , error ) {
1041- v := reflect .ValueOf (opts )
1042- if v .Kind () == reflect .Ptr && v .IsNil () {
1043- return s , nil
1044- }
1045-
1046- origURL , err := url .Parse (s )
1047- if err != nil {
1048- return s , err
1049- }
1050-
1051- origValues := origURL .Query ()
1052-
1053- // Use the github.com/google/go-querystring library to parse the struct
1054- newValues , err := query .Values (opts )
1055- if err != nil {
1056- return s , err
1057- }
1058-
1059- // Merge the values
1060- for key , values := range newValues {
1061- for _ , value := range values {
1062- origValues .Add (key , value )
1063- }
1064- }
1065-
1066- origURL .RawQuery = origValues .Encode ()
1067- return origURL .String (), nil
1068- }
0 commit comments