Skip to content

Commit 4d6a90a

Browse files
upgrades toolset to leverage google/go-github v77 with the exception of Update and Delete items
1 parent 9ec740e commit 4d6a90a

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
)
@@ -237,27 +234,19 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
237234
return mcp.NewToolResultError(err.Error()), nil
238235
}
239236

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

250-
url, err = addOptions(url, opts)
251-
if err != nil {
252-
return nil, fmt.Errorf("failed to add options to request: %w", err)
240+
opts := &github.ListProjectsOptions{
241+
ListProjectsPaginationOptions: github.ListProjectsPaginationOptions{PerPage: &perPage},
253242
}
254243

255-
httpRequest, err := client.NewRequest("GET", url, nil)
256-
if err != nil {
257-
return nil, fmt.Errorf("failed to create request: %w", err)
244+
if ownerType == "org" {
245+
projectFields, resp, err = client.Projects.ListOrganizationProjectFields(ctx, owner, projectNumber, opts)
246+
} else {
247+
projectFields, resp, err = client.Projects.ListUserProjectFields(ctx, owner, projectNumber, opts)
258248
}
259249

260-
resp, err := client.Do(ctx, httpRequest, &projectFields)
261250
if err != nil {
262251
return ghErrors.NewGitHubAPIErrorResponse(ctx,
263252
"failed to list project fields",
@@ -317,7 +306,7 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
317306
if err != nil {
318307
return mcp.NewToolResultError(err.Error()), nil
319308
}
320-
fieldID, err := RequiredInt(req, "field_id")
309+
fieldID, err := RequiredBigInt(req, "field_id")
321310
if err != nil {
322311
return mcp.NewToolResultError(err.Error()), nil
323312
}
@@ -326,21 +315,15 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
326315
return mcp.NewToolResultError(err.Error()), nil
327316
}
328317

329-
var url string
318+
var resp *github.Response
319+
var projectField *github.ProjectV2Field
320+
330321
if ownerType == "org" {
331-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/fields/%d", owner, projectNumber, fieldID)
322+
projectField, resp, err = client.Projects.GetOrganizationProjectField(ctx, owner, projectNumber, fieldID)
332323
} else {
333-
url = fmt.Sprintf("users/%s/projectsV2/%d/fields/%d", owner, projectNumber, fieldID)
334-
}
335-
336-
projectField := projectV2Field{}
337-
338-
httpRequest, err := client.NewRequest("GET", url, nil)
339-
if err != nil {
340-
return nil, fmt.Errorf("failed to create request: %w", err)
324+
projectField, resp, err = client.Projects.GetUserProjectField(ctx, owner, projectNumber, fieldID)
341325
}
342326

343-
resp, err := client.Do(ctx, httpRequest, &projectField)
344327
if err != nil {
345328
return ghErrors.NewGitHubAPIErrorResponse(ctx,
346329
"failed to get project field",
@@ -416,41 +399,32 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
416399
if err != nil {
417400
return mcp.NewToolResultError(err.Error()), nil
418401
}
419-
fields, err := OptionalStringArrayParam(req, "fields")
402+
fields, err := OptionalBigIntArrayParam(req, "fields")
420403
if err != nil {
421404
return mcp.NewToolResultError(err.Error()), nil
422405
}
423-
424406
client, err := getClient(ctx)
425407
if err != nil {
426408
return mcp.NewToolResultError(err.Error()), nil
427409
}
428410

429-
var url string
430-
if ownerType == "org" {
431-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items", owner, projectNumber)
432-
} else {
433-
url = fmt.Sprintf("users/%s/projectsV2/%d/items", owner, projectNumber)
434-
}
435-
projectItems := []projectV2Item{}
436-
437-
opts := listProjectItemsOptions{
438-
paginationOptions: paginationOptions{PerPage: perPage},
439-
filterQueryOptions: filterQueryOptions{Query: queryStr},
440-
fieldSelectionOptions: fieldSelectionOptions{Fields: fields},
441-
}
411+
var resp *github.Response
412+
var projectItems []*github.ProjectV2Item
442413

443-
url, err = addOptions(url, opts)
444-
if err != nil {
445-
return nil, fmt.Errorf("failed to add options to request: %w", err)
414+
opts := &github.ListProjectItemsOptions{
415+
Fields: fields,
416+
ListProjectsOptions: github.ListProjectsOptions{
417+
ListProjectsPaginationOptions: github.ListProjectsPaginationOptions{PerPage: &perPage},
418+
Query: &queryStr,
419+
},
446420
}
447421

448-
httpRequest, err := client.NewRequest("GET", url, nil)
449-
if err != nil {
450-
return nil, fmt.Errorf("failed to create request: %w", err)
422+
if ownerType == "org" {
423+
projectItems, resp, err = client.Projects.ListOrganizationProjectItems(ctx, owner, projectNumber, opts)
424+
} else {
425+
projectItems, resp, err = client.Projects.ListUserProjectItems(ctx, owner, projectNumber, opts)
451426
}
452427

453-
resp, err := client.Do(ctx, httpRequest, &projectItems)
454428
if err != nil {
455429
return ghErrors.NewGitHubAPIErrorResponse(ctx,
456430
ProjectListFailedError,
@@ -518,11 +492,11 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
518492
if err != nil {
519493
return mcp.NewToolResultError(err.Error()), nil
520494
}
521-
itemID, err := RequiredInt(req, "item_id")
495+
itemID, err := RequiredBigInt(req, "item_id")
522496
if err != nil {
523497
return mcp.NewToolResultError(err.Error()), nil
524498
}
525-
fields, err := OptionalStringArrayParam(req, "fields")
499+
fields, err := OptionalBigIntArrayParam(req, "fields")
526500
if err != nil {
527501
return mcp.NewToolResultError(err.Error()), nil
528502
}
@@ -532,32 +506,21 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
532506
return mcp.NewToolResultError(err.Error()), nil
533507
}
534508

535-
var url string
536-
if ownerType == "org" {
537-
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
538-
} else {
539-
url = fmt.Sprintf("users/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
540-
}
541-
542-
opts := fieldSelectionOptions{}
509+
opts := &github.GetProjectItemOptions{}
543510

544511
if len(fields) > 0 {
545512
opts.Fields = fields
546513
}
547514

548-
url, err = addOptions(url, opts)
549-
if err != nil {
550-
return mcp.NewToolResultError(err.Error()), nil
551-
}
552-
553-
projectItem := projectV2Item{}
515+
var resp *github.Response
516+
var projectItem *github.ProjectV2Item
554517

555-
httpRequest, err := client.NewRequest("GET", url, nil)
556-
if err != nil {
557-
return nil, fmt.Errorf("failed to create request: %w", err)
518+
if ownerType == "org" {
519+
projectItem, resp, err = client.Projects.GetOrganizationProjectItem(ctx, owner, projectNumber, itemID, opts)
520+
} else {
521+
projectItem, resp, err = client.Projects.GetUserProjectItem(ctx, owner, projectNumber, itemID, opts)
558522
}
559523

560-
resp, err := client.Do(ctx, httpRequest, &projectItem)
561524
if err != nil {
562525
return ghErrors.NewGitHubAPIErrorResponse(ctx,
563526
"failed to get project item",
@@ -624,7 +587,7 @@ func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
624587
if err != nil {
625588
return mcp.NewToolResultError(err.Error()), nil
626589
}
627-
itemID, err := RequiredInt(req, "item_id")
590+
itemID, err := RequiredBigInt(req, "item_id")
628591
if err != nil {
629592
return mcp.NewToolResultError(err.Error()), nil
630593
}
@@ -642,24 +605,20 @@ func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
642605
return mcp.NewToolResultError(err.Error()), nil
643606
}
644607

645-
var projectsURL string
646-
if ownerType == "org" {
647-
projectsURL = fmt.Sprintf("orgs/%s/projectsV2/%d/items", owner, projectNumber)
648-
} else {
649-
projectsURL = fmt.Sprintf("users/%s/projectsV2/%d/items", owner, projectNumber)
650-
}
651-
652-
newItem := &newProjectItem{
653-
ID: int64(itemID),
608+
newItem := &github.AddProjectItemOptions{
609+
ID: itemID,
654610
Type: toNewProjectType(itemType),
655611
}
656-
httpRequest, err := client.NewRequest("POST", projectsURL, newItem)
657-
if err != nil {
658-
return nil, fmt.Errorf("failed to create request: %w", err)
612+
613+
var resp *github.Response
614+
var addedItem *github.ProjectV2Item
615+
616+
if ownerType == "org" {
617+
addedItem, resp, err = client.Projects.AddOrganizationProjectItem(ctx, owner, projectNumber, newItem)
618+
} else {
619+
addedItem, resp, err = client.Projects.AddUserProjectItem(ctx, owner, projectNumber, newItem)
659620
}
660-
addedItem := projectV2Item{}
661621

662-
resp, err := client.Do(ctx, httpRequest, &addedItem)
663622
if err != nil {
664623
return ghErrors.NewGitHubAPIErrorResponse(ctx,
665624
ProjectAddFailedError,
@@ -869,11 +828,6 @@ func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
869828
}
870829
}
871830

872-
type newProjectItem struct {
873-
ID int64 `json:"id,omitempty"`
874-
Type string `json:"type,omitempty"`
875-
}
876-
877831
type updateProjectItemPayload struct {
878832
Fields []updateProjectItem `json:"fields"`
879833
}
@@ -883,17 +837,6 @@ type updateProjectItem struct {
883837
Value any `json:"value"`
884838
}
885839

886-
type projectV2Field struct {
887-
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
888-
NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field.
889-
Name string `json:"name,omitempty"` // The display name of the field.
890-
DataType string `json:"data_type,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select").
891-
URL string `json:"url,omitempty"` // The API URL for this field.
892-
Options []*any `json:"options,omitempty"` // Available options for single_select and multi_select fields.
893-
CreatedAt *github.Timestamp `json:"created_at,omitempty"` // The time when this field was created.
894-
UpdatedAt *github.Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated.
895-
}
896-
897840
type projectV2ItemFieldValue struct {
898841
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
899842
Name string `json:"name,omitempty"` // The display name of the field.
@@ -931,26 +874,6 @@ type projectV2ItemContent struct {
931874
URL *string `json:"url,omitempty"`
932875
}
933876

934-
type paginationOptions struct {
935-
PerPage int `url:"per_page,omitempty"`
936-
}
937-
938-
type filterQueryOptions struct {
939-
Query string `url:"q,omitempty"`
940-
}
941-
942-
type fieldSelectionOptions struct {
943-
// Specific list of field IDs to include in the response. If not provided, only the title field is included.
944-
// Example: fields=102589,985201,169875 or fields[]=102589&fields[]=985201&fields[]=169875
945-
Fields []string `url:"fields,omitempty"`
946-
}
947-
948-
type listProjectItemsOptions struct {
949-
paginationOptions
950-
filterQueryOptions
951-
fieldSelectionOptions
952-
}
953-
954877
func toNewProjectType(projType string) string {
955878
switch strings.ToLower(projType) {
956879
case "issue":
@@ -986,28 +909,6 @@ func buildUpdateProjectItem(input map[string]any) (*updateProjectItem, error) {
986909
return payload, nil
987910
}
988911

989-
// addOptions adds the parameters in opts as URL query parameters to s. opts
990-
// must be a struct whose fields may contain "url" tags.
991-
func addOptions(s string, opts any) (string, error) {
992-
v := reflect.ValueOf(opts)
993-
if v.Kind() == reflect.Ptr && v.IsNil() {
994-
return s, nil
995-
}
996-
997-
u, err := url.Parse(s)
998-
if err != nil {
999-
return s, err
1000-
}
1001-
1002-
qs, err := query.Values(opts)
1003-
if err != nil {
1004-
return s, err
1005-
}
1006-
1007-
u.RawQuery = qs.Encode()
1008-
return u.String(), nil
1009-
}
1010-
1011912
func ManageProjectItemsPrompt(t translations.TranslationHelperFunc) (tool mcp.Prompt, handler server.PromptHandlerFunc) {
1012913
return mcp.NewPrompt("ManageProjectItems",
1013914
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)