Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

MCP server updates usinggoogle/go-github APIs#1408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 55 additions & 167 deletionspkg/github/projects.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,14 +6,11 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"strings"

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/go-github/v79/github"
"github.com/google/go-querystring/query"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
Expand DownExpand Up@@ -256,30 +253,19 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
return mcp.NewToolResultError(err.Error()), nil
}

var resp *github.Response
var projectFields []*github.ProjectV2Field

opts := &github.ListProjectsOptions{
ListProjectsPaginationOptions: pagination,
}

var url string
if ownerType == "org" {
url = fmt.Sprintf("orgs/%s/projectsV2/%d/fields", owner, projectNumber)
projectFields, resp, err = client.Projects.ListOrganizationProjectFields(ctx, owner, projectNumber, opts)
} else {
url = fmt.Sprintf("users/%s/projectsV2/%d/fields", owner, projectNumber)
}

url, err = addOptions(url, opts)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
projectFields, resp, err = client.Projects.ListUserProjectFields(ctx, owner, projectNumber, opts)
}

httpRequest, err := client.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

var projectFields []projectV2Field
resp, err := client.Do(ctx, httpRequest, &projectFields)

if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to list project fields",
Expand DownExpand Up@@ -452,7 +438,7 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
}

var resp *github.Response
var projectItems []projectV2Item
var projectItems []*github.ProjectV2Item
var queryPtr *string

if queryStr != "" {
Expand All@@ -467,25 +453,12 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
},
}

var url string
if ownerType == "org" {
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items", owner, projectNumber)
projectItems, resp, err = client.Projects.ListOrganizationProjectItems(ctx, owner, projectNumber, opts)
} else {
url = fmt.Sprintf("users/%s/projectsV2/%d/items", owner, projectNumber)
projectItems, resp, err = client.Projects.ListUserProjectItems(ctx, owner, projectNumber, opts)
}

url, err = addOptions(url, opts)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

httpRequest, err := client.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

resp, err = client.Do(ctx, httpRequest, &projectItems)

if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
ProjectListFailedError,
Expand DownExpand Up@@ -566,32 +539,22 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
return mcp.NewToolResultError(err.Error()), nil
}

var url string
if ownerType == "org" {
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
} else {
url = fmt.Sprintf("users/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
}

opts := fieldSelectionOptions{}
var resp *github.Response
var projectItem *github.ProjectV2Item
var opts *github.GetProjectItemOptions

if len(fields) > 0 {
opts.Fields = fields
}

url, err = addOptions(url, opts)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
opts = &github.GetProjectItemOptions{
Fields: fields,
}
}

projectItem := projectV2Item{}

httpRequest, err := client.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
if ownerType == "org" {
projectItem, resp, err = client.Projects.GetOrganizationProjectItem(ctx, owner, projectNumber, itemID, opts)
} else {
projectItem, resp, err = client.Projects.GetUserProjectItem(ctx, owner, projectNumber, itemID, opts)
}

resp, err := client.Do(ctx, httpRequest, &projectItem)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get project item",
Expand DownExpand Up@@ -748,7 +711,7 @@ func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
itemID, err :=RequiredInt(req, "item_id")
itemID, err :=RequiredBigInt(req, "item_id")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
Expand All@@ -773,21 +736,15 @@ func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
return mcp.NewToolResultError(err.Error()), nil
}

var projectsURL string
var resp *github.Response
var updatedItem *github.ProjectV2Item

if ownerType == "org" {
projectsURL = fmt.Sprintf("orgs/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
updatedItem, resp, err = client.Projects.UpdateOrganizationProjectItem(ctx, owner, projectNumber, itemID, updatePayload)
} else {
projectsURL = fmt.Sprintf("users/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
}
httpRequest, err := client.NewRequest("PATCH", projectsURL, updateProjectItemPayload{
Fields: []updateProjectItem{*updatePayload},
})
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
updatedItem, resp, err = client.Projects.UpdateUserProjectItem(ctx, owner, projectNumber, itemID, updatePayload)
}
updatedItem := projectV2Item{}

resp, err := client.Do(ctx, httpRequest, &updatedItem)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
ProjectUpdateFailedError,
Expand DownExpand Up@@ -886,76 +843,13 @@ func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
}
}

type fieldSelectionOptions struct {
// Specific list of field IDs to include in the response. If not provided, only the title field is included.
// The comma tag encodes the slice as comma-separated values: fields=102589,985201,169875
Fields []int64 `url:"fields,omitempty,comma"`
}

type updateProjectItemPayload struct {
Fields []updateProjectItem `json:"fields"`
}

type updateProjectItem struct {
ID int `json:"id"`
Value any `json:"value"`
}

type projectV2ItemFieldValue struct {
ID *int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DataType string `json:"data_type,omitempty"`
Value any `json:"value,omitempty"`
}

type projectV2Item struct {
ArchivedAt *github.Timestamp `json:"archived_at,omitempty"`
Content *projectV2ItemContent `json:"content,omitempty"`
ContentType *string `json:"content_type,omitempty"`
CreatedAt *github.Timestamp `json:"created_at,omitempty"`
Creator *github.User `json:"creator,omitempty"`
Description *string `json:"description,omitempty"`
Fields []*projectV2ItemFieldValue `json:"fields,omitempty"`
ID *int64 `json:"id,omitempty"`
ItemURL *string `json:"item_url,omitempty"`
NodeID *string `json:"node_id,omitempty"`
ProjectURL *string `json:"project_url,omitempty"`
Title *string `json:"title,omitempty"`
UpdatedAt *github.Timestamp `json:"updated_at,omitempty"`
}

type projectV2ItemContent struct {
Body *string `json:"body,omitempty"`
ClosedAt *github.Timestamp `json:"closed_at,omitempty"`
CreatedAt *github.Timestamp `json:"created_at,omitempty"`
ID *int64 `json:"id,omitempty"`
Number *int `json:"number,omitempty"`
State *string `json:"state,omitempty"`
StateReason *string `json:"stateReason,omitempty"`
Title *string `json:"title,omitempty"`
UpdatedAt *github.Timestamp `json:"updated_at,omitempty"`
URL *string `json:"url,omitempty"`
}

type pageInfo struct {
HasNextPage bool `json:"hasNextPage"`
HasPreviousPage bool `json:"hasPreviousPage"`
NextCursor string `json:"nextCursor,omitempty"`
PrevCursor string `json:"prevCursor,omitempty"`
}

type projectV2Field struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
DataType *string `json:"data_type,omitempty"`
ProjectURL *string `json:"project_url,omitempty"`
Options []any `json:"options,omitempty"`
Configuration any `json:"configuration,omitempty"`
CreatedAt *github.Timestamp `json:"created_at,omitempty"`
UpdatedAt *github.Timestamp `json:"updated_at,omitempty"`
}

func toNewProjectType(projType string) string {
switch strings.ToLower(projType) {
case "issue":
Expand All@@ -967,7 +861,27 @@ func toNewProjectType(projType string) string {
}
}

func buildUpdateProjectItem(input map[string]any) (*updateProjectItem, error) {
// validateAndConvertToInt64 ensures the value is a number and converts it to int64.
func validateAndConvertToInt64(value any) (int64, error) {
switch v := value.(type) {
case float64:
// Validate that the float64 can be safely converted to int64
intVal := int64(v)
if float64(intVal) != v {
return 0, fmt.Errorf("value must be a valid integer (got %v)", v)
}
return intVal, nil
case int64:
return v, nil
case int:
return int64(v), nil
default:
return 0, fmt.Errorf("value must be a number (got %T)", v)
}
}

// buildUpdateProjectItem constructs UpdateProjectItemOptions from the input map.
func buildUpdateProjectItem(input map[string]any) (*github.UpdateProjectItemOptions, error) {
if input == nil {
return nil, fmt.Errorf("updated_field must be an object")
}
Expand All@@ -977,16 +891,22 @@ func buildUpdateProjectItem(input map[string]any) (*updateProjectItem, error) {
return nil, fmt.Errorf("updated_field.id is required")
}

idFieldAsFloat64, ok :=idField.(float64) // JSON numbers are float64
if!ok {
return nil, fmt.Errorf("updated_field.id must be a number")
fieldID, err :=validateAndConvertToInt64(idField)
iferr != nil {
return nil, fmt.Errorf("updated_field.id: %w", err)
}

valueField, ok := input["value"]
if !ok {
return nil, fmt.Errorf("updated_field.value is required")
}
payload := &updateProjectItem{ID: int(idFieldAsFloat64), Value: valueField}

payload := &github.UpdateProjectItemOptions{
Fields: []*github.UpdateProjectV2Field{{
ID: fieldID,
Value: valueField,
}},
}

return payload, nil
}
Expand DownExpand Up@@ -1034,35 +954,3 @@ func extractPaginationOptions(request mcp.CallToolRequest) (github.ListProjectsP

return opts, nil
}

// addOptions adds the parameters in opts as URL query parameters to s. opts
// must be a struct whose fields may contain "url" tags.
func addOptions(s string, opts any) (string, error) {
v := reflect.ValueOf(opts)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}

origURL, err := url.Parse(s)
if err != nil {
return s, err
}

origValues := origURL.Query()

// Use the github.com/google/go-querystring library to parse the struct
newValues, err := query.Values(opts)
if err != nil {
return s, err
}

// Merge the values
for key, values := range newValues {
for _, value := range values {
origValues.Add(key, value)
}
}

origURL.RawQuery = origValues.Encode()
return origURL.String(), nil
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp