Skip to content

Commit fcf7fd5

Browse files
upgrades toolset to leverage google/go-github v77 with the exception of Update and Delete items
1 parent 3bf6616 commit fcf7fd5

File tree

1 file changed

+47
-146
lines changed

1 file changed

+47
-146
lines changed

pkg/github/projects.go

Lines changed: 47 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -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/v77/github"
16-
"github.com/google/go-querystring/query"
1714
"github.com/mark3labs/mcp-go/mcp"
1815
"github.com/mark3labs/mcp-go/server"
1916
)
@@ -232,27 +229,19 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
232229
return mcp.NewToolResultError(err.Error()), nil
233230
}
234231

235-
var url string
236-
if ownerType == "org" {
237-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/fields", owner, projectNumber)
238-
} else {
239-
url = fmt.Sprintf("users/%s/projectsV2/%d/fields", owner, projectNumber)
240-
}
241-
projectFields := []projectV2Field{}
242-
243-
opts := paginationOptions{PerPage: perPage}
232+
var resp *github.Response
233+
var projectFields []*github.ProjectV2Field
244234

245-
url, err = addOptions(url, opts)
246-
if err != nil {
247-
return nil, fmt.Errorf("failed to add options to request: %w", err)
235+
opts := &github.ListProjectsOptions{
236+
ListProjectsPaginationOptions: github.ListProjectsPaginationOptions{PerPage: &perPage},
248237
}
249238

250-
httpRequest, err := client.NewRequest("GET", url, nil)
251-
if err != nil {
252-
return nil, fmt.Errorf("failed to create request: %w", err)
239+
if ownerType == "org" {
240+
projectFields, resp, err = client.Projects.ListOrganizationProjectFields(ctx, owner, projectNumber, opts)
241+
} else {
242+
projectFields, resp, err = client.Projects.ListUserProjectFields(ctx, owner, projectNumber, opts)
253243
}
254244

255-
resp, err := client.Do(ctx, httpRequest, &projectFields)
256245
if err != nil {
257246
return ghErrors.NewGitHubAPIErrorResponse(ctx,
258247
"failed to list project fields",
@@ -312,7 +301,7 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
312301
if err != nil {
313302
return mcp.NewToolResultError(err.Error()), nil
314303
}
315-
fieldID, err := RequiredInt(req, "field_id")
304+
fieldID, err := RequiredBigInt(req, "field_id")
316305
if err != nil {
317306
return mcp.NewToolResultError(err.Error()), nil
318307
}
@@ -321,21 +310,15 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
321310
return mcp.NewToolResultError(err.Error()), nil
322311
}
323312

324-
var url string
313+
var resp *github.Response
314+
var projectField *github.ProjectV2Field
315+
325316
if ownerType == "org" {
326-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/fields/%d", owner, projectNumber, fieldID)
317+
projectField, resp, err = client.Projects.GetOrganizationProjectField(ctx, owner, projectNumber, fieldID)
327318
} else {
328-
url = fmt.Sprintf("users/%s/projectsV2/%d/fields/%d", owner, projectNumber, fieldID)
329-
}
330-
331-
projectField := projectV2Field{}
332-
333-
httpRequest, err := client.NewRequest("GET", url, nil)
334-
if err != nil {
335-
return nil, fmt.Errorf("failed to create request: %w", err)
319+
projectField, resp, err = client.Projects.GetUserProjectField(ctx, owner, projectNumber, fieldID)
336320
}
337321

338-
resp, err := client.Do(ctx, httpRequest, &projectField)
339322
if err != nil {
340323
return ghErrors.NewGitHubAPIErrorResponse(ctx,
341324
"failed to get project field",
@@ -411,41 +394,32 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
411394
if err != nil {
412395
return mcp.NewToolResultError(err.Error()), nil
413396
}
414-
fields, err := OptionalStringArrayParam(req, "fields")
397+
fields, err := OptionalBigIntArrayParam(req, "fields")
415398
if err != nil {
416399
return mcp.NewToolResultError(err.Error()), nil
417400
}
418-
419401
client, err := getClient(ctx)
420402
if err != nil {
421403
return mcp.NewToolResultError(err.Error()), nil
422404
}
423405

424-
var url string
425-
if ownerType == "org" {
426-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items", owner, projectNumber)
427-
} else {
428-
url = fmt.Sprintf("users/%s/projectsV2/%d/items", owner, projectNumber)
429-
}
430-
projectItems := []projectV2Item{}
431-
432-
opts := listProjectItemsOptions{
433-
paginationOptions: paginationOptions{PerPage: perPage},
434-
filterQueryOptions: filterQueryOptions{Query: queryStr},
435-
fieldSelectionOptions: fieldSelectionOptions{Fields: fields},
436-
}
406+
var resp *github.Response
407+
var projectItems []*github.ProjectV2Item
437408

438-
url, err = addOptions(url, opts)
439-
if err != nil {
440-
return nil, fmt.Errorf("failed to add options to request: %w", err)
409+
opts := &github.ListProjectItemsOptions{
410+
Fields: fields,
411+
ListProjectsOptions: github.ListProjectsOptions{
412+
ListProjectsPaginationOptions: github.ListProjectsPaginationOptions{PerPage: &perPage},
413+
Query: &queryStr,
414+
},
441415
}
442416

443-
httpRequest, err := client.NewRequest("GET", url, nil)
444-
if err != nil {
445-
return nil, fmt.Errorf("failed to create request: %w", err)
417+
if ownerType == "org" {
418+
projectItems, resp, err = client.Projects.ListOrganizationProjectItems(ctx, owner, projectNumber, opts)
419+
} else {
420+
projectItems, resp, err = client.Projects.ListUserProjectItems(ctx, owner, projectNumber, opts)
446421
}
447422

448-
resp, err := client.Do(ctx, httpRequest, &projectItems)
449423
if err != nil {
450424
return ghErrors.NewGitHubAPIErrorResponse(ctx,
451425
ProjectListFailedError,
@@ -513,11 +487,11 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
513487
if err != nil {
514488
return mcp.NewToolResultError(err.Error()), nil
515489
}
516-
itemID, err := RequiredInt(req, "item_id")
490+
itemID, err := RequiredBigInt(req, "item_id")
517491
if err != nil {
518492
return mcp.NewToolResultError(err.Error()), nil
519493
}
520-
fields, err := OptionalStringArrayParam(req, "fields")
494+
fields, err := OptionalBigIntArrayParam(req, "fields")
521495
if err != nil {
522496
return mcp.NewToolResultError(err.Error()), nil
523497
}
@@ -527,32 +501,21 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
527501
return mcp.NewToolResultError(err.Error()), nil
528502
}
529503

530-
var url string
531-
if ownerType == "org" {
532-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
533-
} else {
534-
url = fmt.Sprintf("users/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
535-
}
536-
537-
opts := fieldSelectionOptions{}
504+
opts := &github.GetProjectItemOptions{}
538505

539506
if len(fields) > 0 {
540507
opts.Fields = fields
541508
}
542509

543-
url, err = addOptions(url, opts)
544-
if err != nil {
545-
return mcp.NewToolResultError(err.Error()), nil
546-
}
547-
548-
projectItem := projectV2Item{}
510+
var resp *github.Response
511+
var projectItem *github.ProjectV2Item
549512

550-
httpRequest, err := client.NewRequest("GET", url, nil)
551-
if err != nil {
552-
return nil, fmt.Errorf("failed to create request: %w", err)
513+
if ownerType == "org" {
514+
projectItem, resp, err = client.Projects.GetOrganizationProjectItem(ctx, owner, projectNumber, itemID, opts)
515+
} else {
516+
projectItem, resp, err = client.Projects.GetUserProjectItem(ctx, owner, projectNumber, itemID, opts)
553517
}
554518

555-
resp, err := client.Do(ctx, httpRequest, &projectItem)
556519
if err != nil {
557520
return ghErrors.NewGitHubAPIErrorResponse(ctx,
558521
"failed to get project item",
@@ -619,7 +582,7 @@ func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
619582
if err != nil {
620583
return mcp.NewToolResultError(err.Error()), nil
621584
}
622-
itemID, err := RequiredInt(req, "item_id")
585+
itemID, err := RequiredBigInt(req, "item_id")
623586
if err != nil {
624587
return mcp.NewToolResultError(err.Error()), nil
625588
}
@@ -637,24 +600,20 @@ func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
637600
return mcp.NewToolResultError(err.Error()), nil
638601
}
639602

640-
var projectsURL string
641-
if ownerType == "org" {
642-
projectsURL = fmt.Sprintf("orgs/%s/projectsV2/%d/items", owner, projectNumber)
643-
} else {
644-
projectsURL = fmt.Sprintf("users/%s/projectsV2/%d/items", owner, projectNumber)
645-
}
646-
647-
newItem := &newProjectItem{
648-
ID: int64(itemID),
603+
newItem := &github.AddProjectItemOptions{
604+
ID: itemID,
649605
Type: toNewProjectType(itemType),
650606
}
651-
httpRequest, err := client.NewRequest("POST", projectsURL, newItem)
652-
if err != nil {
653-
return nil, fmt.Errorf("failed to create request: %w", err)
607+
608+
var resp *github.Response
609+
var addedItem *github.ProjectV2Item
610+
611+
if ownerType == "org" {
612+
addedItem, resp, err = client.Projects.AddOrganizationProjectItem(ctx, owner, projectNumber, newItem)
613+
} else {
614+
addedItem, resp, err = client.Projects.AddUserProjectItem(ctx, owner, projectNumber, newItem)
654615
}
655-
addedItem := projectV2Item{}
656616

657-
resp, err := client.Do(ctx, httpRequest, &addedItem)
658617
if err != nil {
659618
return ghErrors.NewGitHubAPIErrorResponse(ctx,
660619
ProjectAddFailedError,
@@ -864,11 +823,6 @@ func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
864823
}
865824
}
866825

867-
type newProjectItem struct {
868-
ID int64 `json:"id,omitempty"`
869-
Type string `json:"type,omitempty"`
870-
}
871-
872826
type updateProjectItemPayload struct {
873827
Fields []updateProjectItem `json:"fields"`
874828
}
@@ -878,17 +832,6 @@ type updateProjectItem struct {
878832
Value any `json:"value"`
879833
}
880834

881-
type projectV2Field struct {
882-
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
883-
NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field.
884-
Name string `json:"name,omitempty"` // The display name of the field.
885-
DataType string `json:"data_type,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select").
886-
URL string `json:"url,omitempty"` // The API URL for this field.
887-
Options []*any `json:"options,omitempty"` // Available options for single_select and multi_select fields.
888-
CreatedAt *github.Timestamp `json:"created_at,omitempty"` // The time when this field was created.
889-
UpdatedAt *github.Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated.
890-
}
891-
892835
type projectV2ItemFieldValue struct {
893836
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
894837
Name string `json:"name,omitempty"` // The display name of the field.
@@ -926,26 +869,6 @@ type projectV2ItemContent struct {
926869
URL *string `json:"url,omitempty"`
927870
}
928871

929-
type paginationOptions struct {
930-
PerPage int `url:"per_page,omitempty"`
931-
}
932-
933-
type filterQueryOptions struct {
934-
Query string `url:"q,omitempty"`
935-
}
936-
937-
type fieldSelectionOptions struct {
938-
// Specific list of field IDs to include in the response. If not provided, only the title field is included.
939-
// Example: fields=102589,985201,169875 or fields[]=102589&fields[]=985201&fields[]=169875
940-
Fields []string `url:"fields,omitempty"`
941-
}
942-
943-
type listProjectItemsOptions struct {
944-
paginationOptions
945-
filterQueryOptions
946-
fieldSelectionOptions
947-
}
948-
949872
func toNewProjectType(projType string) string {
950873
switch strings.ToLower(projType) {
951874
case "issue":
@@ -981,28 +904,6 @@ func buildUpdateProjectItem(input map[string]any) (*updateProjectItem, error) {
981904
return payload, nil
982905
}
983906

984-
// addOptions adds the parameters in opts as URL query parameters to s. opts
985-
// must be a struct whose fields may contain "url" tags.
986-
func addOptions(s string, opts any) (string, error) {
987-
v := reflect.ValueOf(opts)
988-
if v.Kind() == reflect.Ptr && v.IsNil() {
989-
return s, nil
990-
}
991-
992-
u, err := url.Parse(s)
993-
if err != nil {
994-
return s, err
995-
}
996-
997-
qs, err := query.Values(opts)
998-
if err != nil {
999-
return s, err
1000-
}
1001-
1002-
u.RawQuery = qs.Encode()
1003-
return u.String(), nil
1004-
}
1005-
1006907
func ManageProjectItemsPrompt(t translations.TranslationHelperFunc) (tool mcp.Prompt, handler server.PromptHandlerFunc) {
1007908
return mcp.NewPrompt("ManageProjectItems",
1008909
mcp.WithPromptDescription(t("PROMPT_MANAGE_PROJECT_ITEMS_DESCRIPTION", "Interactive guide for managing GitHub Projects V2, including discovery, field management, querying, and updates.")),

0 commit comments

Comments
 (0)