From 05fb2065b29a47c1c6230bc4d0e7b9ef2d104fb1 Mon Sep 17 00:00:00 2001 From: David Blass Date: Thu, 30 Oct 2025 10:52:01 -0400 Subject: [PATCH] initial version of pr review tools --- fixtures/basic.txt | 2 +- mcp/prInfo.ts | 34 ++++++++++++++++++++++++++++++++++ mcp/review.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ mcp/server.ts | 4 +++- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 mcp/prInfo.ts create mode 100644 mcp/review.ts diff --git a/fixtures/basic.txt b/fixtures/basic.txt index 4aef6c4..5364edc 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -create a PR implementing bogosort to https://github.com/pullfrogai/scratch/ +add a github review for the following PR: https://github.com/pullfrogai/scratch/pull/14 diff --git a/mcp/prInfo.ts b/mcp/prInfo.ts new file mode 100644 index 0000000..7c94128 --- /dev/null +++ b/mcp/prInfo.ts @@ -0,0 +1,34 @@ +import { type } from "arktype"; +import { contextualize, tool } from "./shared.ts"; + +export const PullRequestInfo = type({ + pull_number: type.number.describe("The pull request number to fetch"), +}); + +export const PullRequestInfoTool = tool({ + name: "get_pull_request", + description: "Retrieve detailed information and diff for a specific pull request by number.", + parameters: PullRequestInfo, + execute: contextualize(async ({ pull_number }, ctx) => { + const pr = await ctx.octokit.rest.pulls.get({ + owner: ctx.owner, + repo: ctx.name, + pull_number, + }); + + // Fetch diff using raw request + const diff = await ctx.octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { + owner: ctx.owner, + repo: ctx.name, + pull_number, + headers: { + Accept: 'application/vnd.github.v3.diff', + }, + }); + + return { + ...pr.data, + diff: diff.data, + }; + }), +}); diff --git a/mcp/review.ts b/mcp/review.ts new file mode 100644 index 0000000..cd0918a --- /dev/null +++ b/mcp/review.ts @@ -0,0 +1,46 @@ +import { type } from "arktype"; +import { contextualize, tool } from "./shared.ts"; +import type { RestEndpointMethodTypes } from "@octokit/rest"; + +export const Review = type({ + pull_number: type.number.describe("The pull request number to review"), + event: type.enumerated("APPROVE", "REQUEST_CHANGES", "COMMENT").describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"), + body: type.string.describe("The body content for the review (required for REQUEST_CHANGES or COMMENT)").optional(), + commit_id: type.string.describe("Optional SHA of the commit being reviewed. Defaults to latest.").optional(), + comments: type +({ + path: type.string.describe("The file path to comment on"), + position: type.number.describe("The diff position in the file"), + body: type.string.describe("The comment text"), + }) + .array() + .describe("Array of draft review comments for specific lines, optional.") + .optional() +}); + +export const ReviewTool = tool({ + name: "submit_pull_request_review", + description: "Submit a review (approve, request changes, or comment) for an existing pull request.", + parameters: Review, + execute: contextualize(async ({ pull_number, event, body, commit_id, comments = [] }, ctx) => { + // Compose the request + const params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"] = { + owner: ctx.owner, + repo: ctx.name, + pull_number, + event, + }; + if (body) params.body = body; + if (commit_id) params.commit_id = commit_id; + if (comments.length > 0) params.comments = comments; + const result = await ctx.octokit.rest.pulls.createReview(params); + return { + success: true, + reviewId: result.data.id, + html_url: result.data.html_url, + state: result.data.state, + user: result.data.user?.login, + submitted_at: result.data.submitted_at, + }; + }), +}); diff --git a/mcp/server.ts b/mcp/server.ts index debb792..19c4e9d 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -4,6 +4,8 @@ import { FastMCP } from "fastmcp"; import { CommentTool } from "./comment.ts"; import { IssueTool } from "./issue.ts"; import { PullRequestTool } from "./pr.ts"; +import { ReviewTool } from "./review.ts"; +import { PullRequestInfoTool } from "./prInfo.ts"; import { addTools } from "./shared.ts"; const server = new FastMCP({ @@ -11,6 +13,6 @@ const server = new FastMCP({ version: "0.0.1", }); -addTools(server, [CommentTool, IssueTool, PullRequestTool]); +addTools(server, [CommentTool, IssueTool, PullRequestTool, ReviewTool, PullRequestInfoTool]); server.start();