mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-17 23:53:24 +02:00
Allow either line or position in PR review comments, but not both, to align with GitHub API functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
import { z } from "zod";
|
|
import { githubRequest } from "../common/utils.js";
|
|
import {
|
|
GitHubPullRequestSchema,
|
|
GitHubIssueAssigneeSchema,
|
|
GitHubRepositorySchema,
|
|
} from "../common/types.js";
|
|
|
|
// Schema definitions
|
|
export const PullRequestFileSchema = z.object({
|
|
sha: z.string(),
|
|
filename: z.string(),
|
|
status: z.enum(['added', 'removed', 'modified', 'renamed', 'copied', 'changed', 'unchanged']),
|
|
additions: z.number(),
|
|
deletions: z.number(),
|
|
changes: z.number(),
|
|
blob_url: z.string(),
|
|
raw_url: z.string(),
|
|
contents_url: z.string(),
|
|
patch: z.string().optional()
|
|
});
|
|
|
|
export const StatusCheckSchema = z.object({
|
|
url: z.string(),
|
|
state: z.enum(['error', 'failure', 'pending', 'success']),
|
|
description: z.string().nullable(),
|
|
target_url: z.string().nullable(),
|
|
context: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
|
|
export const CombinedStatusSchema = z.object({
|
|
state: z.enum(['error', 'failure', 'pending', 'success']),
|
|
statuses: z.array(StatusCheckSchema),
|
|
sha: z.string(),
|
|
total_count: z.number()
|
|
});
|
|
|
|
export const PullRequestCommentSchema = z.object({
|
|
url: z.string(),
|
|
id: z.number(),
|
|
node_id: z.string(),
|
|
pull_request_review_id: z.number().nullable(),
|
|
diff_hunk: z.string(),
|
|
path: z.string().nullable(),
|
|
position: z.number().nullable(),
|
|
original_position: z.number().nullable(),
|
|
commit_id: z.string(),
|
|
original_commit_id: z.string(),
|
|
user: GitHubIssueAssigneeSchema,
|
|
body: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string(),
|
|
html_url: z.string(),
|
|
pull_request_url: z.string(),
|
|
author_association: z.string(),
|
|
_links: z.object({
|
|
self: z.object({ href: z.string() }),
|
|
html: z.object({ href: z.string() }),
|
|
pull_request: z.object({ href: z.string() })
|
|
})
|
|
});
|
|
|
|
export const PullRequestReviewSchema = z.object({
|
|
id: z.number(),
|
|
node_id: z.string(),
|
|
user: GitHubIssueAssigneeSchema,
|
|
body: z.string().nullable(),
|
|
state: z.enum(['APPROVED', 'CHANGES_REQUESTED', 'COMMENTED', 'DISMISSED', 'PENDING']),
|
|
html_url: z.string(),
|
|
pull_request_url: z.string(),
|
|
commit_id: z.string(),
|
|
submitted_at: z.string().nullable(),
|
|
author_association: z.string()
|
|
});
|
|
|
|
// Input schemas
|
|
export const CreatePullRequestSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
title: z.string().describe("Pull request title"),
|
|
body: z.string().optional().describe("Pull request body/description"),
|
|
head: z.string().describe("The name of the branch where your changes are implemented"),
|
|
base: z.string().describe("The name of the branch you want the changes pulled into"),
|
|
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
|
|
maintainer_can_modify: z.boolean().optional().describe("Whether maintainers can modify the pull request")
|
|
});
|
|
|
|
export const GetPullRequestSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number")
|
|
});
|
|
|
|
export const ListPullRequestsSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
state: z.enum(['open', 'closed', 'all']).optional().describe("State of the pull requests to return"),
|
|
head: z.string().optional().describe("Filter by head user or head organization and branch name"),
|
|
base: z.string().optional().describe("Filter by base branch name"),
|
|
sort: z.enum(['created', 'updated', 'popularity', 'long-running']).optional().describe("What to sort results by"),
|
|
direction: z.enum(['asc', 'desc']).optional().describe("The direction of the sort"),
|
|
per_page: z.number().optional().describe("Results per page (max 100)"),
|
|
page: z.number().optional().describe("Page number of the results")
|
|
});
|
|
|
|
export const CreatePullRequestReviewSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number"),
|
|
commit_id: z.string().optional().describe("The SHA of the commit that needs a review"),
|
|
body: z.string().describe("The body text of the review"),
|
|
event: z.enum(['APPROVE', 'REQUEST_CHANGES', 'COMMENT']).describe("The review action to perform"),
|
|
comments: z.array(
|
|
z.union([
|
|
z.object({
|
|
path: z.string().describe("The relative path to the file being commented on"),
|
|
position: z.number().describe("The position in the diff where you want to add a review comment"),
|
|
body: z.string().describe("Text of the review comment")
|
|
}),
|
|
z.object({
|
|
path: z.string().describe("The relative path to the file being commented on"),
|
|
line: z.number().describe("The line number in the file where you want to add a review comment"),
|
|
body: z.string().describe("Text of the review comment")
|
|
})
|
|
])
|
|
).optional().describe("Comments to post as part of the review (specify either position or line, not both)")
|
|
});
|
|
|
|
export const MergePullRequestSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number"),
|
|
commit_title: z.string().optional().describe("Title for the automatic commit message"),
|
|
commit_message: z.string().optional().describe("Extra detail to append to automatic commit message"),
|
|
merge_method: z.enum(['merge', 'squash', 'rebase']).optional().describe("Merge method to use")
|
|
});
|
|
|
|
export const GetPullRequestFilesSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number")
|
|
});
|
|
|
|
export const GetPullRequestStatusSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number")
|
|
});
|
|
|
|
export const UpdatePullRequestBranchSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number"),
|
|
expected_head_sha: z.string().optional().describe("The expected SHA of the pull request's HEAD ref")
|
|
});
|
|
|
|
export const GetPullRequestCommentsSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number")
|
|
});
|
|
|
|
export const GetPullRequestReviewsSchema = z.object({
|
|
owner: z.string().describe("Repository owner (username or organization)"),
|
|
repo: z.string().describe("Repository name"),
|
|
pull_number: z.number().describe("Pull request number")
|
|
});
|
|
|
|
// Function implementations
|
|
export async function createPullRequest(
|
|
params: z.infer<typeof CreatePullRequestSchema>
|
|
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
|
const { owner, repo, ...options } = CreatePullRequestSchema.parse(params);
|
|
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls`,
|
|
{
|
|
method: "POST",
|
|
body: options,
|
|
}
|
|
);
|
|
|
|
return GitHubPullRequestSchema.parse(response);
|
|
}
|
|
|
|
export async function getPullRequest(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number
|
|
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`
|
|
);
|
|
return GitHubPullRequestSchema.parse(response);
|
|
}
|
|
|
|
export async function listPullRequests(
|
|
owner: string,
|
|
repo: string,
|
|
options: Omit<z.infer<typeof ListPullRequestsSchema>, 'owner' | 'repo'>
|
|
): Promise<z.infer<typeof GitHubPullRequestSchema>[]> {
|
|
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/pulls`);
|
|
|
|
if (options.state) url.searchParams.append('state', options.state);
|
|
if (options.head) url.searchParams.append('head', options.head);
|
|
if (options.base) url.searchParams.append('base', options.base);
|
|
if (options.sort) url.searchParams.append('sort', options.sort);
|
|
if (options.direction) url.searchParams.append('direction', options.direction);
|
|
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
|
if (options.page) url.searchParams.append('page', options.page.toString());
|
|
|
|
const response = await githubRequest(url.toString());
|
|
return z.array(GitHubPullRequestSchema).parse(response);
|
|
}
|
|
|
|
export async function createPullRequestReview(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number,
|
|
options: Omit<z.infer<typeof CreatePullRequestReviewSchema>, 'owner' | 'repo' | 'pull_number'>
|
|
): Promise<z.infer<typeof PullRequestReviewSchema>> {
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`,
|
|
{
|
|
method: 'POST',
|
|
body: options,
|
|
}
|
|
);
|
|
return PullRequestReviewSchema.parse(response);
|
|
}
|
|
|
|
export async function mergePullRequest(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number,
|
|
options: Omit<z.infer<typeof MergePullRequestSchema>, 'owner' | 'repo' | 'pull_number'>
|
|
): Promise<any> {
|
|
return githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`,
|
|
{
|
|
method: 'PUT',
|
|
body: options,
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getPullRequestFiles(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number
|
|
): Promise<z.infer<typeof PullRequestFileSchema>[]> {
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`
|
|
);
|
|
return z.array(PullRequestFileSchema).parse(response);
|
|
}
|
|
|
|
export async function updatePullRequestBranch(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number,
|
|
expectedHeadSha?: string
|
|
): Promise<void> {
|
|
await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/update-branch`,
|
|
{
|
|
method: "PUT",
|
|
body: expectedHeadSha ? { expected_head_sha: expectedHeadSha } : undefined,
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getPullRequestComments(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number
|
|
): Promise<z.infer<typeof PullRequestCommentSchema>[]> {
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`
|
|
);
|
|
return z.array(PullRequestCommentSchema).parse(response);
|
|
}
|
|
|
|
export async function getPullRequestReviews(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number
|
|
): Promise<z.infer<typeof PullRequestReviewSchema>[]> {
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`
|
|
);
|
|
return z.array(PullRequestReviewSchema).parse(response);
|
|
}
|
|
|
|
export async function getPullRequestStatus(
|
|
owner: string,
|
|
repo: string,
|
|
pullNumber: number
|
|
): Promise<z.infer<typeof CombinedStatusSchema>> {
|
|
// First get the PR to get the head SHA
|
|
const pr = await getPullRequest(owner, repo, pullNumber);
|
|
const sha = pr.head.sha;
|
|
|
|
// Then get the combined status for that SHA
|
|
const response = await githubRequest(
|
|
`https://api.github.com/repos/${owner}/${repo}/commits/${sha}/status`
|
|
);
|
|
return CombinedStatusSchema.parse(response);
|
|
} |