Skip to content

Commit 39f8045

Browse files
committed
Create 'reprioritize sub-issue' tool
1 parent a0e757a commit 39f8045

File tree

3 files changed

+477
-0
lines changed

3 files changed

+477
-0
lines changed

pkg/github/issues.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,139 @@ func RemoveSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc)
467467
}
468468
}
469469

470+
// ReprioritizeSubIssue creates a tool to reprioritize a sub-issue to a different position in the parent list.
471+
func ReprioritizeSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
472+
return mcp.NewTool("reprioritize_sub_issue",
473+
mcp.WithDescription(t("TOOL_REPRIORITIZE_SUB_ISSUE_DESCRIPTION", "Reprioritize a sub-issue to a different position in the parent issue's sub-issue list.")),
474+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
475+
Title: t("TOOL_REPRIORITIZE_SUB_ISSUE_USER_TITLE", "Reprioritize sub-issue"),
476+
ReadOnlyHint: toBoolPtr(false),
477+
}),
478+
mcp.WithString("owner",
479+
mcp.Required(),
480+
mcp.Description("Repository owner"),
481+
),
482+
mcp.WithString("repo",
483+
mcp.Required(),
484+
mcp.Description("Repository name"),
485+
),
486+
mcp.WithNumber("issue_number",
487+
mcp.Required(),
488+
mcp.Description("The number of the parent issue"),
489+
),
490+
mcp.WithNumber("sub_issue_id",
491+
mcp.Required(),
492+
mcp.Description("The ID of the sub-issue to reprioritize"),
493+
),
494+
mcp.WithNumber("after_id",
495+
mcp.Description("The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified)"),
496+
),
497+
mcp.WithNumber("before_id",
498+
mcp.Description("The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified)"),
499+
),
500+
),
501+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
502+
owner, err := requiredParam[string](request, "owner")
503+
if err != nil {
504+
return mcp.NewToolResultError(err.Error()), nil
505+
}
506+
repo, err := requiredParam[string](request, "repo")
507+
if err != nil {
508+
return mcp.NewToolResultError(err.Error()), nil
509+
}
510+
issueNumber, err := RequiredInt(request, "issue_number")
511+
if err != nil {
512+
return mcp.NewToolResultError(err.Error()), nil
513+
}
514+
subIssueID, err := RequiredInt(request, "sub_issue_id")
515+
if err != nil {
516+
return mcp.NewToolResultError(err.Error()), nil
517+
}
518+
519+
// Handle optional positioning parameters
520+
afterID, err := OptionalIntParam(request, "after_id")
521+
if err != nil {
522+
return mcp.NewToolResultError(err.Error()), nil
523+
}
524+
beforeID, err := OptionalIntParam(request, "before_id")
525+
if err != nil {
526+
return mcp.NewToolResultError(err.Error()), nil
527+
}
528+
529+
// Validate that either after_id or before_id is specified, but not both
530+
if afterID == 0 && beforeID == 0 {
531+
return mcp.NewToolResultError("either after_id or before_id must be specified"), nil
532+
}
533+
if afterID != 0 && beforeID != 0 {
534+
return mcp.NewToolResultError("only one of after_id or before_id should be specified, not both"), nil
535+
}
536+
537+
client, err := getClient(ctx)
538+
if err != nil {
539+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
540+
}
541+
542+
// Create the request body
543+
requestBody := map[string]interface{}{
544+
"sub_issue_id": subIssueID,
545+
}
546+
if afterID != 0 {
547+
requestBody["after_id"] = afterID
548+
}
549+
if beforeID != 0 {
550+
requestBody["before_id"] = beforeID
551+
}
552+
553+
// Since the go-github library might not have sub-issues support yet,
554+
// we'll make a direct HTTP request using the client's HTTP client
555+
reqBodyBytes, err := json.Marshal(requestBody)
556+
if err != nil {
557+
return nil, fmt.Errorf("failed to marshal request body: %w", err)
558+
}
559+
560+
url := fmt.Sprintf("%srepos/%s/%s/issues/%d/sub_issues/priority",
561+
client.BaseURL.String(), owner, repo, issueNumber)
562+
req, err := http.NewRequestWithContext(ctx, "PATCH", url, strings.NewReader(string(reqBodyBytes)))
563+
if err != nil {
564+
return nil, fmt.Errorf("failed to create request: %w", err)
565+
}
566+
567+
req.Header.Set("Accept", "application/vnd.github+json")
568+
req.Header.Set("Content-Type", "application/json")
569+
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
570+
571+
// Use the same authentication as the GitHub client
572+
httpClient := client.Client()
573+
resp, err := httpClient.Do(req)
574+
if err != nil {
575+
return nil, fmt.Errorf("failed to reprioritize sub-issue: %w", err)
576+
}
577+
defer func() { _ = resp.Body.Close() }()
578+
579+
body, err := io.ReadAll(resp.Body)
580+
if err != nil {
581+
return nil, fmt.Errorf("failed to read response body: %w", err)
582+
}
583+
584+
if resp.StatusCode != http.StatusOK {
585+
return mcp.NewToolResultError(fmt.Sprintf("failed to reprioritize sub-issue: %s", string(body))), nil
586+
}
587+
588+
// Parse and re-marshal to ensure consistent formatting
589+
var result interface{}
590+
if err := json.Unmarshal(body, &result); err != nil {
591+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
592+
}
593+
594+
r, err := json.Marshal(result)
595+
if err != nil {
596+
return nil, fmt.Errorf("failed to marshal response: %w", err)
597+
}
598+
599+
return mcp.NewToolResultText(string(r)), nil
600+
}
601+
}
602+
470603
// SearchIssues creates a tool to search for issues and pull requests.
471604
func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
472605
return mcp.NewTool("search_issues",

0 commit comments

Comments
 (0)