Skip to content
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
38 changes: 38 additions & 0 deletions pkg/github/minimal_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,44 @@ type MinimalCheckRunsResult struct {
CheckRuns []MinimalCheckRun `json:"check_runs"`
}

// MinimalReviewerUser is a user requested to review a pull request.
type MinimalReviewerUser struct {
Login string `json:"login"`
HTMLURL string `json:"html_url,omitempty"`
}

// MinimalReviewerTeam is a team requested to review a pull request.
type MinimalReviewerTeam struct {
Slug string `json:"slug"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
}

// MinimalPRReviewers is the output type for PR requested reviewers.
type MinimalPRReviewers struct {
Users []MinimalReviewerUser `json:"users,omitempty"`
Teams []MinimalReviewerTeam `json:"teams,omitempty"`
}

// MinimalCommitStatus is a single commit status entry.
type MinimalCommitStatus struct {
State string `json:"state"`
Context string `json:"context"`
Description string `json:"description,omitempty"`
TargetURL string `json:"target_url,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}

// MinimalStatusChecks combines legacy commit statuses and modern check runs.
type MinimalStatusChecks struct {
CombinedState string `json:"combined_state"`
Statuses []MinimalCommitStatus `json:"statuses,omitempty"`
CheckRuns []MinimalCheckRun `json:"check_runs,omitempty"`
TotalCount int `json:"total_count"`
}

// convertToMinimalCheckRun converts a GitHub API CheckRun to MinimalCheckRun
func convertToMinimalCheckRun(checkRun *github.CheckRun) MinimalCheckRun {
minimalCheckRun := MinimalCheckRun{
Expand Down
118 changes: 117 additions & 1 deletion pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ Possible options:
6. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method. Use with pagination parameters to control the number of results returned.
7. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.
8. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.
9. get_reviewers - Get the list of requested reviewers (users and teams) for a pull request who have not yet submitted a review.
10. get_status_checks - Get a unified view of all status checks for a pull request, combining legacy commit statuses and modern check runs.
`,
Enum: []any{"get", "get_diff", "get_status", "get_files", "get_review_comments", "get_reviews", "get_comments", "get_check_runs"},
Enum: []any{"get", "get_diff", "get_status", "get_files", "get_review_comments", "get_reviews", "get_comments", "get_check_runs", "get_reviewers", "get_status_checks"},
},
"owner": {
Type: "string",
Expand Down Expand Up @@ -139,6 +141,12 @@ Possible options:
case "get_check_runs":
result, err := GetPullRequestCheckRuns(ctx, client, owner, repo, pullNumber, pagination)
return result, nil, err
case "get_reviewers":
result, err := GetPullRequestReviewers(ctx, client, owner, repo, pullNumber)
return result, nil, err
case "get_status_checks":
result, err := GetPullRequestStatusChecks(ctx, client, owner, repo, pullNumber)
return result, nil, err
default:
return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
}
Expand Down Expand Up @@ -343,6 +351,114 @@ func GetPullRequestCheckRuns(ctx context.Context, client *github.Client, owner,
return utils.NewToolResultText(string(r)), nil
}

func GetPullRequestReviewers(ctx context.Context, client *github.Client, owner, repo string, pullNumber int) (*mcp.CallToolResult, error) {
reviewers, resp, err := client.PullRequests.ListReviewers(ctx, owner, repo, pullNumber)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get pull request reviewers", resp, err), nil
}
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 ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get pull request reviewers", resp, body), nil
}

result := MinimalPRReviewers{}
for _, u := range reviewers.Users {
result.Users = append(result.Users, MinimalReviewerUser{
Login: u.GetLogin(),
HTMLURL: u.GetHTMLURL(),
})
}
for _, t := range reviewers.Teams {
result.Teams = append(result.Teams, MinimalReviewerTeam{
Slug: t.GetSlug(),
Name: t.GetName(),
Description: t.GetDescription(),
HTMLURL: t.GetHTMLURL(),
})
}

return MarshalledTextResult(result), nil
}

func GetPullRequestStatusChecks(ctx context.Context, client *github.Client, owner, repo string, pullNumber int) (*mcp.CallToolResult, error) {
pr, resp, err := client.PullRequests.Get(ctx, owner, repo, pullNumber)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get pull request", resp, err), nil
}
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 ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get pull request", resp, body), nil
}
Comment on lines +395 to +401

sha := pr.GetHead().GetSHA()

combinedStatus, resp, err := client.Repositories.GetCombinedStatus(ctx, owner, repo, sha, nil)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get combined status", resp, err), nil
}
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 ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get combined status", resp, body), nil
}

checkRuns, resp, err := client.Checks.ListCheckRunsForRef(ctx, owner, repo, sha, nil)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get check runs", resp, err), nil
}
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 ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get check runs", resp, body), nil
}

result := MinimalStatusChecks{
CombinedState: combinedStatus.GetState(),
}

for _, s := range combinedStatus.Statuses {
ms := MinimalCommitStatus{
State: s.GetState(),
Context: s.GetContext(),
Description: s.GetDescription(),
TargetURL: s.GetTargetURL(),
}
if s.CreatedAt != nil {
ms.CreatedAt = s.CreatedAt.Format("2006-01-02T15:04:05Z")
}
if s.UpdatedAt != nil {
ms.UpdatedAt = s.UpdatedAt.Format("2006-01-02T15:04:05Z")
}
result.Statuses = append(result.Statuses, ms)
}

for _, cr := range checkRuns.CheckRuns {
result.CheckRuns = append(result.CheckRuns, convertToMinimalCheckRun(cr))
}

result.TotalCount = len(result.Statuses) + len(result.CheckRuns)

return MarshalledTextResult(result), nil
}

func GetPullRequestFiles(ctx context.Context, client *github.Client, owner, repo string, pullNumber int, pagination PaginationParams) (*mcp.CallToolResult, error) {
opts := &github.ListOptions{
PerPage: pagination.PerPage,
Expand Down