Add support for listing, reading, and reviewing PRs
This commit is contained in:
parent
d373bb8376
commit
9e25ffd599
3 changed files with 190 additions and 0 deletions
|
|
@ -188,6 +188,43 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- `issue_number` (number): Issue number to retrieve
|
- `issue_number` (number): Issue number to retrieve
|
||||||
- Returns: Github Issue object & details
|
- Returns: Github Issue object & details
|
||||||
|
|
||||||
|
18. `get_pull_request`
|
||||||
|
- Get details of a specific pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- Returns: Pull request details including diff and review status
|
||||||
|
|
||||||
|
19. `list_pull_requests`
|
||||||
|
- List and filter repository pull requests
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `state` (optional string): Filter by state ('open', 'closed', 'all')
|
||||||
|
- `head` (optional string): Filter by head user/org and branch
|
||||||
|
- `base` (optional string): Filter by base branch
|
||||||
|
- `sort` (optional string): Sort by ('created', 'updated', 'popularity', 'long-running')
|
||||||
|
- `direction` (optional string): Sort direction ('asc', 'desc')
|
||||||
|
- `per_page` (optional number): Results per page (max 100)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- Returns: Array of pull request details
|
||||||
|
|
||||||
|
20. `create_pull_request_review`
|
||||||
|
- Create a review on a pull request
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `pull_number` (number): Pull request number
|
||||||
|
- `body` (string): Review comment text
|
||||||
|
- `event` (string): Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT')
|
||||||
|
- `commit_id` (optional string): SHA of commit to review
|
||||||
|
- `comments` (optional array): Line-specific comments, each with:
|
||||||
|
- `path` (string): File path
|
||||||
|
- `position` (number): Line position in diff
|
||||||
|
- `body` (string): Comment text
|
||||||
|
- Returns: Created review details
|
||||||
|
|
||||||
## Search Query Syntax
|
## Search Query Syntax
|
||||||
|
|
||||||
### Code Search
|
### Code Search
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
ForkRepositorySchema,
|
ForkRepositorySchema,
|
||||||
GetFileContentsSchema,
|
GetFileContentsSchema,
|
||||||
GetIssueSchema,
|
GetIssueSchema,
|
||||||
|
GetPullRequestSchema,
|
||||||
GitHubCommitSchema,
|
GitHubCommitSchema,
|
||||||
GitHubContentSchema,
|
GitHubContentSchema,
|
||||||
GitHubCreateUpdateFileResponseSchema,
|
GitHubCreateUpdateFileResponseSchema,
|
||||||
|
|
@ -36,6 +37,8 @@ import {
|
||||||
IssueCommentSchema,
|
IssueCommentSchema,
|
||||||
ListCommitsSchema,
|
ListCommitsSchema,
|
||||||
ListIssuesOptionsSchema,
|
ListIssuesOptionsSchema,
|
||||||
|
ListPullRequestsSchema,
|
||||||
|
CreatePullRequestReviewSchema,
|
||||||
PushFilesSchema,
|
PushFilesSchema,
|
||||||
SearchCodeResponseSchema,
|
SearchCodeResponseSchema,
|
||||||
SearchCodeSchema,
|
SearchCodeSchema,
|
||||||
|
|
@ -715,6 +718,86 @@ async function getIssue(
|
||||||
return GitHubIssueSchema.parse(await response.json());
|
return GitHubIssueSchema.parse(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getPullRequest(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<GitHubPullRequest> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitHubPullRequestSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listPullRequests(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: Omit<z.infer<typeof ListPullRequestsSchema>, 'owner' | 'repo'>
|
||||||
|
): Promise<GitHubPullRequest[]> {
|
||||||
|
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 fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return z.array(GitHubPullRequestSchema).parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPullRequestReview(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
pullNumber: number,
|
||||||
|
options: Omit<z.infer<typeof CreatePullRequestReviewSchema>, 'owner' | 'repo' | 'pull_number'>
|
||||||
|
): Promise<any> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(options),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
tools: [
|
tools: [
|
||||||
|
|
@ -806,6 +889,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
name: "get_issue",
|
name: "get_issue",
|
||||||
description: "Get details of a specific issue in a GitHub repository.",
|
description: "Get details of a specific issue in a GitHub repository.",
|
||||||
inputSchema: zodToJsonSchema(GetIssueSchema)
|
inputSchema: zodToJsonSchema(GetIssueSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get_pull_request",
|
||||||
|
description: "Get details of a specific pull request in a GitHub repository",
|
||||||
|
inputSchema: zodToJsonSchema(GetPullRequestSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list_pull_requests",
|
||||||
|
description: "List pull requests in a GitHub repository with filtering options",
|
||||||
|
inputSchema: zodToJsonSchema(ListPullRequestsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_pull_request_review",
|
||||||
|
description: "Create a review on a pull request",
|
||||||
|
inputSchema: zodToJsonSchema(CreatePullRequestReviewSchema)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
@ -1011,6 +1109,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
return { toolResult: issue };
|
return { toolResult: issue };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "get_pull_request": {
|
||||||
|
const args = GetPullRequestSchema.parse(request.params.arguments);
|
||||||
|
const pullRequest = await getPullRequest(args.owner, args.repo, args.pull_number);
|
||||||
|
return { toolResult: pullRequest };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_pull_requests": {
|
||||||
|
const args = ListPullRequestsSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, ...options } = args;
|
||||||
|
const pullRequests = await listPullRequests(owner, repo, options);
|
||||||
|
return { toolResult: pullRequests };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "create_pull_request_review": {
|
||||||
|
const args = CreatePullRequestReviewSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, pull_number, ...options } = args;
|
||||||
|
const review = await createPullRequestReview(owner, repo, pull_number, options);
|
||||||
|
return { toolResult: review };
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -683,6 +683,38 @@ export const GetIssueSchema = z.object({
|
||||||
issue_number: z.number().describe("Issue number")
|
issue_number: z.number().describe("Issue number")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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.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")
|
||||||
|
})).optional().describe("Comments to post as part of the review")
|
||||||
|
});
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
||||||
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
||||||
|
|
@ -717,3 +749,6 @@ export type SearchIssueItem = z.infer<typeof SearchIssueItemSchema>;
|
||||||
export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>;
|
export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>;
|
||||||
export type SearchUserItem = z.infer<typeof SearchUserItemSchema>;
|
export type SearchUserItem = z.infer<typeof SearchUserItemSchema>;
|
||||||
export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;
|
export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;
|
||||||
|
export type GetPullRequest = z.infer<typeof GetPullRequestSchema>;
|
||||||
|
export type ListPullRequests = z.infer<typeof ListPullRequestsSchema>;
|
||||||
|
export type CreatePullRequestReview = z.infer<typeof CreatePullRequestReviewSchema>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue