package github import ( "context" "encoding/json" "fmt" "io" "net/http" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // GetPullRequest creates a tool to get details of a specific pull request. func GetPullRequest(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request", mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_DESCRIPTION", "Get details of a specific pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pr, resp, err := client.PullRequests.Get(ctx, owner, repo, pullNumber) if err != nil { return nil, fmt.Errorf("failed to get pull request: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request: %s", string(body))), nil } r, err := json.Marshal(pr) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // ListPullRequests creates a tool to list and filter repository pull requests. func ListPullRequests(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_pull_requests", mcp.WithDescription(t("TOOL_LIST_PULL_REQUESTS_DESCRIPTION", "List and filter repository pull requests")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithString("state", mcp.Description("Filter by state ('open', 'closed', 'all')"), ), mcp.WithString("head", mcp.Description("Filter by head user/org and branch"), ), mcp.WithString("base", mcp.Description("Filter by base branch"), ), mcp.WithString("sort", mcp.Description("Sort by ('created', 'updated', 'popularity', 'long-running')"), ), mcp.WithString("direction", mcp.Description("Sort direction ('asc', 'desc')"), ), WithPagination(), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } state, err := OptionalParam[string](request, "state") if err != nil { return mcp.NewToolResultError(err.Error()), nil } head, err := OptionalParam[string](request, "head") if err != nil { return mcp.NewToolResultError(err.Error()), nil } base, err := OptionalParam[string](request, "base") if err != nil { return mcp.NewToolResultError(err.Error()), nil } sort, err := OptionalParam[string](request, "sort") if err != nil { return mcp.NewToolResultError(err.Error()), nil } direction, err := OptionalParam[string](request, "direction") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.PullRequestListOptions{ State: state, Head: head, Base: base, Sort: sort, Direction: direction, ListOptions: github.ListOptions{ PerPage: pagination.perPage, Page: pagination.page, }, } prs, resp, err := client.PullRequests.List(ctx, owner, repo, opts) if err != nil { return nil, fmt.Errorf("failed to list pull requests: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to list pull requests: %s", string(body))), nil } r, err := json.Marshal(prs) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // MergePullRequest creates a tool to merge a pull request. func MergePullRequest(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("merge_pull_request", mcp.WithDescription(t("TOOL_MERGE_PULL_REQUEST_DESCRIPTION", "Merge a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), mcp.WithString("commit_title", mcp.Description("Title for merge commit"), ), mcp.WithString("commit_message", mcp.Description("Extra detail for merge commit"), ), mcp.WithString("merge_method", mcp.Description("Merge method ('merge', 'squash', 'rebase')"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } commitTitle, err := OptionalParam[string](request, "commit_title") if err != nil { return mcp.NewToolResultError(err.Error()), nil } commitMessage, err := OptionalParam[string](request, "commit_message") if err != nil { return mcp.NewToolResultError(err.Error()), nil } mergeMethod, err := OptionalParam[string](request, "merge_method") if err != nil { return mcp.NewToolResultError(err.Error()), nil } options := &github.PullRequestOptions{ CommitTitle: commitTitle, MergeMethod: mergeMethod, } result, resp, err := client.PullRequests.Merge(ctx, owner, repo, pullNumber, commitMessage, options) if err != nil { return nil, fmt.Errorf("failed to merge pull request: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to merge pull request: %s", string(body))), nil } r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // GetPullRequestFiles creates a tool to get the list of files changed in a pull request. func GetPullRequestFiles(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_files", mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_FILES_DESCRIPTION", "Get the list of files changed in a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.ListOptions{} files, resp, err := client.PullRequests.ListFiles(ctx, owner, repo, pullNumber, opts) if err != nil { return nil, fmt.Errorf("failed to get pull request files: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request files: %s", string(body))), nil } r, err := json.Marshal(files) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // GetPullRequestStatus creates a tool to get the combined status of all status checks for a pull request. func GetPullRequestStatus(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_status", mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_STATUS_DESCRIPTION", "Get the combined status of all status checks for a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } // First get the PR to find the head SHA pr, resp, err := client.PullRequests.Get(ctx, owner, repo, pullNumber) if err != nil { return nil, fmt.Errorf("failed to get pull request: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request: %s", string(body))), nil } // Get combined status for the head SHA status, resp, err := client.Repositories.GetCombinedStatus(ctx, owner, repo, *pr.Head.SHA, nil) if err != nil { return nil, fmt.Errorf("failed to get combined status: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get combined status: %s", string(body))), nil } r, err := json.Marshal(status) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // UpdatePullRequestBranch creates a tool to update a pull request branch with the latest changes from the base branch. func UpdatePullRequestBranch(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("update_pull_request_branch", mcp.WithDescription(t("TOOL_UPDATE_PULL_REQUEST_BRANCH_DESCRIPTION", "Update a pull request branch with the latest changes from the base branch")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), mcp.WithString("expectedHeadSha", mcp.Description("The expected SHA of the pull request's HEAD ref"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } expectedHeadSHA, err := OptionalParam[string](request, "expectedHeadSha") if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.PullRequestBranchUpdateOptions{} if expectedHeadSHA != "" { opts.ExpectedHeadSHA = github.Ptr(expectedHeadSHA) } result, resp, err := client.PullRequests.UpdateBranch(ctx, owner, repo, pullNumber, opts) if err != nil { // Check if it's an acceptedError. An acceptedError indicates that the update is in progress, // and it's not a real error. if resp != nil && resp.StatusCode == http.StatusAccepted && isAcceptedError(err) { return mcp.NewToolResultText("Pull request branch update is in progress"), nil } return nil, fmt.Errorf("failed to update pull request branch: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusAccepted { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to update pull request branch: %s", string(body))), nil } r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // GetPullRequestComments creates a tool to get the review comments on a pull request. func GetPullRequestComments(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_comments", mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_COMMENTS_DESCRIPTION", "Get the review comments on a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.PullRequestListCommentsOptions{ ListOptions: github.ListOptions{ PerPage: 100, }, } comments, resp, err := client.PullRequests.ListComments(ctx, owner, repo, pullNumber, opts) if err != nil { return nil, fmt.Errorf("failed to get pull request comments: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request comments: %s", string(body))), nil } r, err := json.Marshal(comments) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // GetPullRequestReviews creates a tool to get the reviews on a pull request. func GetPullRequestReviews(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_reviews", mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_REVIEWS_DESCRIPTION", "Get the reviews on a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } reviews, resp, err := client.PullRequests.ListReviews(ctx, owner, repo, pullNumber, nil) if err != nil { return nil, fmt.Errorf("failed to get pull request reviews: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request reviews: %s", string(body))), nil } r, err := json.Marshal(reviews) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // CreatePullRequestReview creates a tool to submit a review on a pull request. func CreatePullRequestReview(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_pull_request_review", mcp.WithDescription(t("TOOL_CREATE_PULL_REQUEST_REVIEW_DESCRIPTION", "Create a review on a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithNumber("pullNumber", mcp.Required(), mcp.Description("Pull request number"), ), mcp.WithString("body", mcp.Description("Review comment text"), ), mcp.WithString("event", mcp.Required(), mcp.Description("Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT')"), ), mcp.WithString("commitId", mcp.Description("SHA of commit to review"), ), mcp.WithArray("comments", mcp.Items( map[string]interface{}{ "type": "object", "additionalProperties": false, "required": []string{"path", "body"}, "properties": map[string]interface{}{ "path": map[string]interface{}{ "type": "string", "description": "path to the file", }, "position": map[string]interface{}{ "type": "number", "description": "position of the comment in the diff", }, "line": map[string]interface{}{ "type": "number", "description": "line number in the file to comment on. For multi-line comments, the end of the line range", }, "side": map[string]interface{}{ "type": "string", "description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)", }, "start_line": map[string]interface{}{ "type": "number", "description": "The first line of the range to which the comment refers. Required for multi-line comments.", }, "start_side": map[string]interface{}{ "type": "string", "description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)", }, "body": map[string]interface{}{ "type": "string", "description": "comment body", }, }, }, ), mcp.Description("Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters."), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pullNumber, err := RequiredInt(request, "pullNumber") if err != nil { return mcp.NewToolResultError(err.Error()), nil } event, err := requiredParam[string](request, "event") if err != nil { return mcp.NewToolResultError(err.Error()), nil } // Create review request reviewRequest := &github.PullRequestReviewRequest{ Event: github.Ptr(event), } // Add body if provided body, err := OptionalParam[string](request, "body") if err != nil { return mcp.NewToolResultError(err.Error()), nil } if body != "" { reviewRequest.Body = github.Ptr(body) } // Add commit ID if provided commitID, err := OptionalParam[string](request, "commitId") if err != nil { return mcp.NewToolResultError(err.Error()), nil } if commitID != "" { reviewRequest.CommitID = github.Ptr(commitID) } // Add comments if provided if commentsObj, ok := request.Params.Arguments["comments"].([]interface{}); ok && len(commentsObj) > 0 { comments := []*github.DraftReviewComment{} for _, c := range commentsObj { commentMap, ok := c.(map[string]interface{}) if !ok { return mcp.NewToolResultError("each comment must be an object with path and body"), nil } path, ok := commentMap["path"].(string) if !ok || path == "" { return mcp.NewToolResultError("each comment must have a path"), nil } body, ok := commentMap["body"].(string) if !ok || body == "" { return mcp.NewToolResultError("each comment must have a body"), nil } _, hasPosition := commentMap["position"].(float64) _, hasLine := commentMap["line"].(float64) _, hasSide := commentMap["side"].(string) _, hasStartLine := commentMap["start_line"].(float64) _, hasStartSide := commentMap["start_side"].(string) switch { case !hasPosition && !hasLine: return mcp.NewToolResultError("each comment must have either position or line"), nil case hasPosition && (hasLine || hasSide || hasStartLine || hasStartSide): return mcp.NewToolResultError("position cannot be combined with line, side, start_line, or start_side"), nil case hasStartSide && !hasSide: return mcp.NewToolResultError("if start_side is provided, side must also be provided"), nil } comment := &github.DraftReviewComment{ Path: github.Ptr(path), Body: github.Ptr(body), } if positionFloat, ok := commentMap["position"].(float64); ok { comment.Position = github.Ptr(int(positionFloat)) } else if lineFloat, ok := commentMap["line"].(float64); ok { comment.Line = github.Ptr(int(lineFloat)) } if side, ok := commentMap["side"].(string); ok { comment.Side = github.Ptr(side) } if startLineFloat, ok := commentMap["start_line"].(float64); ok { comment.StartLine = github.Ptr(int(startLineFloat)) } if startSide, ok := commentMap["start_side"].(string); ok { comment.StartSide = github.Ptr(startSide) } comments = append(comments, comment) } reviewRequest.Comments = comments } review, resp, err := client.PullRequests.CreateReview(ctx, owner, repo, pullNumber, reviewRequest) if err != nil { return nil, fmt.Errorf("failed to create pull request review: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to create pull request review: %s", string(body))), nil } r, err := json.Marshal(review) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } } // CreatePullRequest creates a tool to create a new pull request. func CreatePullRequest(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_pull_request", mcp.WithDescription(t("TOOL_CREATE_PULL_REQUEST_DESCRIPTION", "Create a new pull request in a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), ), mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name"), ), mcp.WithString("title", mcp.Required(), mcp.Description("PR title"), ), mcp.WithString("body", mcp.Description("PR description"), ), mcp.WithString("head", mcp.Required(), mcp.Description("Branch containing changes"), ), mcp.WithString("base", mcp.Required(), mcp.Description("Branch to merge into"), ), mcp.WithBoolean("draft", mcp.Description("Create as draft PR"), ), mcp.WithBoolean("maintainer_can_modify", mcp.Description("Allow maintainer edits"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { owner, err := requiredParam[string](request, "owner") if err != nil { return mcp.NewToolResultError(err.Error()), nil } repo, err := requiredParam[string](request, "repo") if err != nil { return mcp.NewToolResultError(err.Error()), nil } title, err := requiredParam[string](request, "title") if err != nil { return mcp.NewToolResultError(err.Error()), nil } head, err := requiredParam[string](request, "head") if err != nil { return mcp.NewToolResultError(err.Error()), nil } base, err := requiredParam[string](request, "base") if err != nil { return mcp.NewToolResultError(err.Error()), nil } body, err := OptionalParam[string](request, "body") if err != nil { return mcp.NewToolResultError(err.Error()), nil } draft, err := OptionalParam[bool](request, "draft") if err != nil { return mcp.NewToolResultError(err.Error()), nil } maintainerCanModify, err := OptionalParam[bool](request, "maintainer_can_modify") if err != nil { return mcp.NewToolResultError(err.Error()), nil } newPR := &github.NewPullRequest{ Title: github.Ptr(title), Head: github.Ptr(head), Base: github.Ptr(base), } if body != "" { newPR.Body = github.Ptr(body) } newPR.Draft = github.Ptr(draft) newPR.MaintainerCanModify = github.Ptr(maintainerCanModify) pr, resp, err := client.PullRequests.Create(ctx, owner, repo, newPR) if err != nil { return nil, fmt.Errorf("failed to create pull request: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to create pull request: %s", string(body))), nil } r, err := json.Marshal(pr) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } }