Skip to content

Commit 353fbb8

Browse files
committed
Add reading PR files and status, merging PRs
1 parent 9e25ffd commit 353fbb8

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-0
lines changed

src/github/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,33 @@ MCP Server for the GitHub API, enabling file operations, repository management,
225225
- `body` (string): Comment text
226226
- Returns: Created review details
227227

228+
21. `merge_pull_request`
229+
- Merge a pull request
230+
- Inputs:
231+
- `owner` (string): Repository owner
232+
- `repo` (string): Repository name
233+
- `pull_number` (number): Pull request number
234+
- `commit_title` (optional string): Title for merge commit
235+
- `commit_message` (optional string): Extra detail for merge commit
236+
- `merge_method` (optional string): Merge method ('merge', 'squash', 'rebase')
237+
- Returns: Merge result details
238+
239+
22. `get_pull_request_files`
240+
- Get the list of files changed in a pull request
241+
- Inputs:
242+
- `owner` (string): Repository owner
243+
- `repo` (string): Repository name
244+
- `pull_number` (number): Pull request number
245+
- Returns: Array of changed files with patch and status details
246+
247+
23. `get_pull_request_status`
248+
- Get the combined status of all status checks for a pull request
249+
- Inputs:
250+
- `owner` (string): Repository owner
251+
- `repo` (string): Repository name
252+
- `pull_number` (number): Pull request number
253+
- Returns: Combined status check results and individual check details
254+
228255
## Search Query Syntax
229256

230257
### Code Search

src/github/index.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
GetFileContentsSchema,
2323
GetIssueSchema,
2424
GetPullRequestSchema,
25+
GetPullRequestFilesSchema,
26+
GetPullRequestStatusSchema,
27+
MergePullRequestSchema,
2528
GitHubCommitSchema,
2629
GitHubContentSchema,
2730
GitHubCreateUpdateFileResponseSchema,
@@ -40,6 +43,8 @@ import {
4043
ListPullRequestsSchema,
4144
CreatePullRequestReviewSchema,
4245
PushFilesSchema,
46+
PullRequestFileSchema,
47+
CombinedStatusSchema,
4348
SearchCodeResponseSchema,
4449
SearchCodeSchema,
4550
SearchIssuesResponseSchema,
@@ -798,6 +803,99 @@ async function createPullRequestReview(
798803
return await response.json();
799804
}
800805

806+
async function mergePullRequest(
807+
owner: string,
808+
repo: string,
809+
pullNumber: number,
810+
options: Omit<z.infer<typeof MergePullRequestSchema>, 'owner' | 'repo' | 'pull_number'>
811+
): Promise<any> {
812+
const response = await fetch(
813+
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`,
814+
{
815+
method: 'PUT',
816+
headers: {
817+
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
818+
Accept: "application/vnd.github.v3+json",
819+
"User-Agent": "github-mcp-server",
820+
"Content-Type": "application/json",
821+
},
822+
body: JSON.stringify(options),
823+
}
824+
);
825+
826+
if (!response.ok) {
827+
throw new Error(`GitHub API error: ${response.statusText}`);
828+
}
829+
830+
return await response.json();
831+
}
832+
833+
async function getPullRequestFiles(
834+
owner: string,
835+
repo: string,
836+
pullNumber: number
837+
): Promise<z.infer<typeof PullRequestFileSchema>[]> {
838+
const response = await fetch(
839+
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`,
840+
{
841+
headers: {
842+
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
843+
Accept: "application/vnd.github.v3+json",
844+
"User-Agent": "github-mcp-server",
845+
},
846+
}
847+
);
848+
849+
if (!response.ok) {
850+
throw new Error(`GitHub API error: ${response.statusText}`);
851+
}
852+
853+
return z.array(PullRequestFileSchema).parse(await response.json());
854+
}
855+
856+
async function getPullRequestStatus(
857+
owner: string,
858+
repo: string,
859+
pullNumber: number
860+
): Promise<z.infer<typeof CombinedStatusSchema>> {
861+
// First get the PR to get the head SHA
862+
const prResponse = await fetch(
863+
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`,
864+
{
865+
headers: {
866+
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
867+
Accept: "application/vnd.github.v3+json",
868+
"User-Agent": "github-mcp-server",
869+
},
870+
}
871+
);
872+
873+
if (!prResponse.ok) {
874+
throw new Error(`GitHub API error: ${prResponse.statusText}`);
875+
}
876+
877+
const pr = GitHubPullRequestSchema.parse(await prResponse.json());
878+
const sha = pr.head.sha;
879+
880+
// Then get the combined status for that SHA
881+
const statusResponse = await fetch(
882+
`https://api.github.com/repos/${owner}/${repo}/commits/${sha}/status`,
883+
{
884+
headers: {
885+
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
886+
Accept: "application/vnd.github.v3+json",
887+
"User-Agent": "github-mcp-server",
888+
},
889+
}
890+
);
891+
892+
if (!statusResponse.ok) {
893+
throw new Error(`GitHub API error: ${statusResponse.statusText}`);
894+
}
895+
896+
return CombinedStatusSchema.parse(await statusResponse.json());
897+
}
898+
801899
server.setRequestHandler(ListToolsRequestSchema, async () => {
802900
return {
803901
tools: [
@@ -904,6 +1002,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
9041002
name: "create_pull_request_review",
9051003
description: "Create a review on a pull request",
9061004
inputSchema: zodToJsonSchema(CreatePullRequestReviewSchema)
1005+
},
1006+
{
1007+
name: "merge_pull_request",
1008+
description: "Merge a pull request",
1009+
inputSchema: zodToJsonSchema(MergePullRequestSchema)
1010+
},
1011+
{
1012+
name: "get_pull_request_files",
1013+
description: "Get the list of files changed in a pull request",
1014+
inputSchema: zodToJsonSchema(GetPullRequestFilesSchema)
1015+
},
1016+
{
1017+
name: "get_pull_request_status",
1018+
description: "Get the combined status of all status checks for a pull request",
1019+
inputSchema: zodToJsonSchema(GetPullRequestStatusSchema)
9071020
}
9081021
],
9091022
};
@@ -1129,6 +1242,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
11291242
return { toolResult: review };
11301243
}
11311244

1245+
case "merge_pull_request": {
1246+
const args = MergePullRequestSchema.parse(request.params.arguments);
1247+
const { owner, repo, pull_number, ...options } = args;
1248+
const result = await mergePullRequest(owner, repo, pull_number, options);
1249+
return { toolResult: result };
1250+
}
1251+
1252+
case "get_pull_request_files": {
1253+
const args = GetPullRequestFilesSchema.parse(request.params.arguments);
1254+
const files = await getPullRequestFiles(args.owner, args.repo, args.pull_number);
1255+
return { toolResult: files };
1256+
}
1257+
1258+
case "get_pull_request_status": {
1259+
const args = GetPullRequestStatusSchema.parse(request.params.arguments);
1260+
const status = await getPullRequestStatus(args.owner, args.repo, args.pull_number);
1261+
return { toolResult: status };
1262+
}
1263+
11321264
default:
11331265
throw new Error(`Unknown tool: ${request.params.name}`);
11341266
}

src/github/schemas.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,3 +752,64 @@ export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;
752752
export type GetPullRequest = z.infer<typeof GetPullRequestSchema>;
753753
export type ListPullRequests = z.infer<typeof ListPullRequestsSchema>;
754754
export type CreatePullRequestReview = z.infer<typeof CreatePullRequestReviewSchema>;
755+
756+
// Schema for merging a pull request
757+
export const MergePullRequestSchema = z.object({
758+
owner: z.string().describe("Repository owner (username or organization)"),
759+
repo: z.string().describe("Repository name"),
760+
pull_number: z.number().describe("Pull request number"),
761+
commit_title: z.string().optional().describe("Title for the automatic commit message"),
762+
commit_message: z.string().optional().describe("Extra detail to append to automatic commit message"),
763+
merge_method: z.enum(['merge', 'squash', 'rebase']).optional().describe("Merge method to use")
764+
});
765+
766+
// Schema for getting PR files
767+
export const GetPullRequestFilesSchema = z.object({
768+
owner: z.string().describe("Repository owner (username or organization)"),
769+
repo: z.string().describe("Repository name"),
770+
pull_number: z.number().describe("Pull request number")
771+
});
772+
773+
export const PullRequestFileSchema = z.object({
774+
sha: z.string(),
775+
filename: z.string(),
776+
status: z.enum(['added', 'removed', 'modified', 'renamed', 'copied', 'changed', 'unchanged']),
777+
additions: z.number(),
778+
deletions: z.number(),
779+
changes: z.number(),
780+
blob_url: z.string(),
781+
raw_url: z.string(),
782+
contents_url: z.string(),
783+
patch: z.string().optional()
784+
});
785+
786+
// Schema for checking PR status
787+
export const GetPullRequestStatusSchema = z.object({
788+
owner: z.string().describe("Repository owner (username or organization)"),
789+
repo: z.string().describe("Repository name"),
790+
pull_number: z.number().describe("Pull request number")
791+
});
792+
793+
export const StatusCheckSchema = z.object({
794+
url: z.string(),
795+
state: z.enum(['error', 'failure', 'pending', 'success']),
796+
description: z.string().nullable(),
797+
target_url: z.string().nullable(),
798+
context: z.string(),
799+
created_at: z.string(),
800+
updated_at: z.string()
801+
});
802+
803+
export const CombinedStatusSchema = z.object({
804+
state: z.enum(['error', 'failure', 'pending', 'success']),
805+
statuses: z.array(StatusCheckSchema),
806+
sha: z.string(),
807+
total_count: z.number()
808+
});
809+
810+
export type MergePullRequest = z.infer<typeof MergePullRequestSchema>;
811+
export type GetPullRequestFiles = z.infer<typeof GetPullRequestFilesSchema>;
812+
export type PullRequestFile = z.infer<typeof PullRequestFileSchema>;
813+
export type GetPullRequestStatus = z.infer<typeof GetPullRequestStatusSchema>;
814+
export type StatusCheck = z.infer<typeof StatusCheckSchema>;
815+
export type CombinedStatus = z.infer<typeof CombinedStatusSchema>;

0 commit comments

Comments
 (0)