Skip to content

Commit 21fcf5e

Browse files
committed
add tool to update project status
1 parent 1090bed commit 21fcf5e

File tree

2 files changed

+151
-41
lines changed

2 files changed

+151
-41
lines changed

pkg/github/projects.go

Lines changed: 150 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -716,49 +716,158 @@ func AddIssueToProject(getClient GetGQLClientFn, t translations.TranslationHelpe
716716
}
717717
}
718718

719-
// UpdateProjectItemField updates a field value on a project item.
720-
func UpdateProjectItemField(getClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
721-
return mcp.NewTool("update_project_item_field",
722-
mcp.WithDescription(t("TOOL_UPDATE_PROJECT_ITEM_FIELD_DESCRIPTION", "Update a project item field")),
723-
mcp.WithToolAnnotation(mcp.ToolAnnotation{Title: t("TOOL_UPDATE_PROJECT_ITEM_FIELD_USER_TITLE", "Update project item field"), ReadOnlyHint: ToBoolPtr(false)}),
724-
mcp.WithString("project_id", mcp.Required(), mcp.Description("Project ID")),
725-
mcp.WithString("item_id", mcp.Required(), mcp.Description("Item ID")),
726-
mcp.WithString("field_id", mcp.Required(), mcp.Description("Field ID")),
727-
mcp.WithString("text_value", mcp.Description("Text value")),
728-
), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
729-
projectID, err := RequiredParam[string](req, "project_id")
730-
if err != nil {
731-
return mcp.NewToolResultError(err.Error()), nil
732-
}
733-
itemID, err := RequiredParam[string](req, "item_id")
734-
if err != nil {
735-
return mcp.NewToolResultError(err.Error()), nil
736-
}
737-
fieldID, err := RequiredParam[string](req, "field_id")
738-
if err != nil {
739-
return mcp.NewToolResultError(err.Error()), nil
740-
}
741-
textValue, err := OptionalParam[string](req, "text_value")
742-
if err != nil {
743-
return mcp.NewToolResultError(err.Error()), nil
744-
}
745-
client, err := getClient(ctx)
746-
if err != nil {
747-
return mcp.NewToolResultError(err.Error()), nil
748-
}
749-
val := githubv4.ProjectV2FieldValue{}
750-
if textValue != "" {
751-
val.Text = githubv4.NewString(githubv4.String(textValue))
752-
}
753-
var mut struct {
754-
UpdateProjectV2ItemFieldValue struct{ Typename githubv4.String } `graphql:"updateProjectV2ItemFieldValue(input: $input)"`
755-
}
756-
input := githubv4.UpdateProjectV2ItemFieldValueInput{ProjectID: githubv4.ID(projectID), ItemID: githubv4.ID(itemID), FieldID: githubv4.ID(fieldID), Value: val}
757-
if err := client.Mutate(ctx, &mut, input, nil); err != nil {
758-
return mcp.NewToolResultError(err.Error()), nil
719+
// UpdateProjectItemStatus updates the status field of a project item, allowing items to be moved between columns.
720+
func UpdateProjectItemStatus(getClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
721+
return mcp.NewTool("update_project_item_status",
722+
mcp.WithDescription(t("TOOL_UPDATE_PROJECT_ITEM_STATUS_DESCRIPTION", "Update a project item's status to move it between columns")),
723+
mcp.WithToolAnnotation(mcp.ToolAnnotation{Title: t("TOOL_UPDATE_PROJECT_ITEM_STATUS_USER_TITLE", "Update project item status"), ReadOnlyHint: ToBoolPtr(false)}),
724+
mcp.WithString("project_id", mcp.Required(), mcp.Description("Project ID")),
725+
mcp.WithString("item_id", mcp.Required(), mcp.Description("Item ID")),
726+
mcp.WithString("status_option_id", mcp.Required(), mcp.Description("Status option ID (use get_project_statuses to find available options)")),
727+
), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
728+
projectID, err := RequiredParam[string](req, "project_id")
729+
if err != nil {
730+
return mcp.NewToolResultError(err.Error()), nil
731+
}
732+
itemID, err := RequiredParam[string](req, "item_id")
733+
if err != nil {
734+
return mcp.NewToolResultError(err.Error()), nil
735+
}
736+
statusOptionID, err := RequiredParam[string](req, "status_option_id")
737+
if err != nil {
738+
return mcp.NewToolResultError(err.Error()), nil
739+
}
740+
741+
client, err := getClient(ctx)
742+
if err != nil {
743+
return mcp.NewToolResultError(err.Error()), nil
744+
}
745+
746+
var statusFieldQuery struct {
747+
Node struct {
748+
ProjectV2 struct {
749+
Field struct {
750+
ProjectV2SingleSelectField struct {
751+
ID githubv4.ID
752+
Name githubv4.String
753+
Options []struct {
754+
ID githubv4.ID
755+
Name githubv4.String
756+
}
757+
} `graphql:"... on ProjectV2SingleSelectField"`
758+
} `graphql:"field(name: \"Status\")"`
759+
} `graphql:"... on ProjectV2"`
760+
} `graphql:"node(id: $projectId)"`
761+
}
762+
763+
variables := map[string]any{
764+
"projectId": githubv4.ID(projectID),
765+
}
766+
767+
if err := client.Query(ctx, &statusFieldQuery, variables); err != nil {
768+
return mcp.NewToolResultError(fmt.Sprintf("Failed to get Status field for project: %s", err.Error())), nil
769+
}
770+
771+
statusField := statusFieldQuery.Node.ProjectV2.Field.ProjectV2SingleSelectField
772+
if statusField.ID == "" {
773+
return mcp.NewToolResultError(fmt.Sprintf("Could not find a Status field for project with ID '%s'. The project might not have a Status field configured.", projectID)), nil
774+
}
775+
776+
// Validate that the provided status option ID exists
777+
var validOption bool
778+
var optionName string
779+
for _, option := range statusField.Options {
780+
if option.ID.(string) == statusOptionID {
781+
validOption = true
782+
optionName = string(option.Name)
783+
break
759784
}
760-
return MarshalledTextResult(mut), nil
761785
}
786+
787+
if !validOption {
788+
return mcp.NewToolResultError(fmt.Sprintf("Invalid status_option_id '%s' for project '%s'. Use get_project_statuses to see available options.", statusOptionID, projectID)), nil
789+
}
790+
791+
val := githubv4.ProjectV2FieldValue{
792+
SingleSelectOptionID: githubv4.NewString(githubv4.String(statusOptionID)),
793+
}
794+
795+
var mut struct {
796+
UpdateProjectV2ItemFieldValue struct {
797+
ProjectV2Item struct {
798+
ID githubv4.ID
799+
} `graphql:"projectV2Item"`
800+
} `graphql:"updateProjectV2ItemFieldValue(input: $input)"`
801+
}
802+
803+
input := githubv4.UpdateProjectV2ItemFieldValueInput{
804+
ProjectID: githubv4.ID(projectID),
805+
ItemID: githubv4.ID(itemID),
806+
FieldID: statusField.ID,
807+
Value: val,
808+
}
809+
810+
if err := client.Mutate(ctx, &mut, input, nil); err != nil {
811+
return mcp.NewToolResultError(fmt.Sprintf("Failed to update project item status: %s", err.Error())), nil
812+
}
813+
814+
result := map[string]interface{}{
815+
"success": true,
816+
"message": fmt.Sprintf("Successfully updated item status to '%s'", optionName),
817+
"project_id": projectID,
818+
"item_id": itemID,
819+
"new_status": optionName,
820+
"status_option_id": statusOptionID,
821+
}
822+
823+
return MarshalledTextResult(result), nil
824+
}
825+
}
826+
827+
828+
// UpdateProjectItemField updates a field value on a project item.
829+
func UpdateProjectItemField(getClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
830+
return mcp.NewTool("update_project_item_field",
831+
mcp.WithDescription(t("TOOL_UPDATE_PROJECT_ITEM_FIELD_DESCRIPTION", "Update a project item field")),
832+
mcp.WithToolAnnotation(mcp.ToolAnnotation{Title: t("TOOL_UPDATE_PROJECT_ITEM_FIELD_USER_TITLE", "Update project item field"), ReadOnlyHint: ToBoolPtr(false)}),
833+
mcp.WithString("project_id", mcp.Required(), mcp.Description("Project ID")),
834+
mcp.WithString("item_id", mcp.Required(), mcp.Description("Item ID")),
835+
mcp.WithString("field_id", mcp.Required(), mcp.Description("Field ID")),
836+
mcp.WithString("text_value", mcp.Description("Text value")),
837+
), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
838+
projectID, err := RequiredParam[string](req, "project_id")
839+
if err != nil {
840+
return mcp.NewToolResultError(err.Error()), nil
841+
}
842+
itemID, err := RequiredParam[string](req, "item_id")
843+
if err != nil {
844+
return mcp.NewToolResultError(err.Error()), nil
845+
}
846+
fieldID, err := RequiredParam[string](req, "field_id")
847+
if err != nil {
848+
return mcp.NewToolResultError(err.Error()), nil
849+
}
850+
textValue, err := OptionalParam[string](req, "text_value")
851+
if err != nil {
852+
return mcp.NewToolResultError(err.Error()), nil
853+
}
854+
client, err := getClient(ctx)
855+
if err != nil {
856+
return mcp.NewToolResultError(err.Error()), nil
857+
}
858+
val := githubv4.ProjectV2FieldValue{}
859+
if textValue != "" {
860+
val.Text = githubv4.NewString(githubv4.String(textValue))
861+
}
862+
var mut struct {
863+
UpdateProjectV2ItemFieldValue struct{ Typename githubv4.String } `graphql:"updateProjectV2ItemFieldValue(input: $input)"`
864+
}
865+
input := githubv4.UpdateProjectV2ItemFieldValueInput{ProjectID: githubv4.ID(projectID), ItemID: githubv4.ID(itemID), FieldID: githubv4.ID(fieldID), Value: val}
866+
if err := client.Mutate(ctx, &mut, input, nil); err != nil {
867+
return mcp.NewToolResultError(err.Error()), nil
868+
}
869+
return MarshalledTextResult(mut), nil
870+
}
762871
}
763872

764873
// CreateDraftIssue creates a draft issue in a project.

pkg/github/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
115115
toolsets.NewServerTool(CreateProjectIssue(getGQLClient, t)),
116116
toolsets.NewServerTool(AddIssueToProject(getGQLClient, t)),
117117
toolsets.NewServerTool(UpdateProjectItemField(getGQLClient, t)),
118+
toolsets.NewServerTool(UpdateProjectItemStatus(getGQLClient, t)),
118119
toolsets.NewServerTool(CreateDraftIssue(getGQLClient, t)),
119120
toolsets.NewServerTool(DeleteProjectItem(getGQLClient, t)),
120121
)

0 commit comments

Comments
 (0)