Skip to content

feat: add reviewers parameter to create_pull_request #814

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ The following sets of tools are available (all are on by default):
- `maintainer_can_modify`: Allow maintainer edits (boolean, optional)
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
- `reviewers`: GitHub usernames to request reviews from (string[], optional)
- `title`: PR title (string, required)

- **delete_pending_pull_request_review** - Delete the requester's latest pending pull request review
Expand Down
7 changes: 7 additions & 0 deletions pkg/github/__toolsnaps__/create_pull_request.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
"description": "Repository name",
"type": "string"
},
"reviewers": {
"description": "GitHub usernames to request reviews from",
"items": {
"type": "string"
},
"type": "array"
},
"title": {
"description": "PR title",
"type": "string"
Expand Down
52 changes: 52 additions & 0 deletions pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ func CreatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
mcp.WithBoolean("maintainer_can_modify",
mcp.Description("Allow maintainer edits"),
),
mcp.WithArray("reviewers",
mcp.Description("GitHub usernames to request reviews from"),
mcp.Items(map[string]interface{}{
"type": "string",
}),
),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
owner, err := RequiredParam[string](request, "owner")
Expand Down Expand Up @@ -158,6 +164,12 @@ func CreatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
return mcp.NewToolResultError(err.Error()), nil
}

// Handle reviewers parameter
reviewers, err := OptionalStringArrayParam(request, "reviewers")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

newPR := &github.NewPullRequest{
Title: github.Ptr(title),
Head: github.Ptr(head),
Expand Down Expand Up @@ -193,6 +205,46 @@ func CreatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
return mcp.NewToolResultError(fmt.Sprintf("failed to create pull request: %s", string(body))), nil
}

// Request reviewers if provided
if len(reviewers) > 0 {
reviewersRequest := github.ReviewersRequest{
Reviewers: reviewers,
}

_, reviewResp, err := client.PullRequests.RequestReviewers(ctx, owner, repo, *pr.Number, reviewersRequest)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to request reviewers",
reviewResp,
err,
), nil
}
defer func() {
if reviewResp != nil && reviewResp.Body != nil {
_ = reviewResp.Body.Close()
}
}()

if reviewResp.StatusCode != http.StatusCreated && reviewResp.StatusCode != http.StatusOK {
body, err := io.ReadAll(reviewResp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
Copy link
Preview

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return statement is inconsistent with the pattern used elsewhere in the function. It should return mcp.NewToolResultError(err.Error()), nil to match the error handling pattern used in lines 165, 170, 189, 205, and 233.

Suggested change
return nil, fmt.Errorf("failed to read response body: %w", err)
return mcp.NewToolResultError(fmt.Sprintf("failed to read response body: %v", err)), nil

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot It seems that the other functions also use this pattern when failing to read response body

Copy link
Preview

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return statement returns a nil *mcp.CallToolResult and an error, which is inconsistent with the function signature that expects (*mcp.CallToolResult, error). This should return an mcp.NewToolResultError instead of returning nil and an error.

Suggested change
return nil, fmt.Errorf("failed to read response body: %w", err)
return mcp.NewToolResultError(fmt.Sprintf("failed to read response body: %v", err)), nil

Copilot uses AI. Check for mistakes.

}
return mcp.NewToolResultError(fmt.Sprintf("failed to request reviewers: %s", string(body))), nil
}

// Refresh PR data to include reviewers
pr, resp, err = client.PullRequests.Get(ctx, owner, repo, *pr.Number)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get updated pull request",
resp,
err,
), nil
}
defer func() { _ = resp.Body.Close() }()
Copy link
Preview

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defer statement for closing resp.Body should include a nil check for resp to avoid potential panics. This should be: defer func() { if resp != nil && resp.Body != nil { _ = resp.Body.Close() } }()

Suggested change
defer func() { _ = resp.Body.Close() }()
defer func() {
if resp != nil && resp.Body != nil {
_ = resp.Body.Close()
}
}()

Copilot uses AI. Check for mistakes.

}

r, err := json.Marshal(pr)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
Expand Down
44 changes: 44 additions & 0 deletions pkg/github/pullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,7 @@ func Test_CreatePullRequest(t *testing.T) {
assert.Contains(t, tool.InputSchema.Properties, "base")
assert.Contains(t, tool.InputSchema.Properties, "draft")
assert.Contains(t, tool.InputSchema.Properties, "maintainer_can_modify")
assert.Contains(t, tool.InputSchema.Properties, "reviewers")
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "title", "head", "base"})

// Setup mock PR for success case
Expand Down Expand Up @@ -1885,6 +1886,49 @@ func Test_CreatePullRequest(t *testing.T) {
expectError: true,
expectedErrMsg: "failed to create pull request",
},
{
name: "successful PR creation with reviewers",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.PostReposPullsByOwnerByRepo,
expectRequestBody(t, map[string]interface{}{
"title": "Test PR with reviewers",
"body": "This PR has reviewers",
"head": "feature-branch",
"base": "main",
"draft": false,
"maintainer_can_modify": true,
}).andThen(
mockResponse(t, http.StatusCreated, mockPR),
),
),
mock.WithRequestMatchHandler(
mock.PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber,
expectRequestBody(t, map[string]interface{}{
"reviewers": []interface{}{"reviewer1", "reviewer2"},
}).andThen(
mockResponse(t, http.StatusCreated, mockPR),
),
),
mock.WithRequestMatch(
mock.GetReposPullsByOwnerByRepoByPullNumber,
mockPR,
),
),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"title": "Test PR with reviewers",
"body": "This PR has reviewers",
"head": "feature-branch",
"base": "main",
"draft": false,
"maintainer_can_modify": true,
"reviewers": []string{"reviewer1", "reviewer2"},
},
expectError: false,
expectedPR: mockPR,
},
}

for _, tc := range tests {
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy