Add additional tools

This commit is contained in:
Colin McDonnell
2025-11-20 00:34:03 -08:00
parent 098df15764
commit 85f8fbfaf5
7 changed files with 185 additions and 15 deletions
+47
View File
@@ -27,6 +27,53 @@ await mcp.call("gh-pullfrog/get_check_suite_logs", {
});
```
### review tools
#### `get_review_comments`
get all line-by-line comments for a specific pull request review.
**parameters:**
- `pull_number` (number): the pull request number
- `review_id` (number): the id from review.id in the webhook payload
**replaces:** `gh api repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments`
**returns:**
array of review comments including:
- file path, line number, comment body
- side (LEFT/RIGHT) and position in diff
- user, timestamps, html_url
- in_reply_to_id for threaded comments
**example:**
```typescript
// when handling a pull_request_review_submitted webhook
await mcp.call("gh-pullfrog/get_review_comments", {
pull_number: 47,
review_id: review.id
});
```
#### `list_pull_request_reviews`
list all reviews for a pull request.
**parameters:**
- `pull_number` (number): the pull request number
**replaces:** `gh api repos/{owner}/{repo}/pulls/{pull_number}/reviews`
**returns:**
array of reviews with:
- review id, body, state (approved/changes_requested/commented)
- user, commit_id, submitted_at, html_url
**example:**
```typescript
await mcp.call("gh-pullfrog/list_pull_request_reviews", {
pull_number: 47
});
```
### other tools
see individual files for documentation on other tools:
+12 -11
View File
@@ -12,17 +12,18 @@ export const GetCheckSuiteLogsTool = tool({
parameters: GetCheckSuiteLogs,
execute: contextualize(async ({ check_suite_id }, ctx) => {
// get workflow runs for this specific check suite
const runsResponse = await ctx.octokit.rest.actions.listWorkflowRunsForRepo({
owner: ctx.owner,
repo: ctx.name,
check_suite_id,
per_page: 100,
});
const failedRuns = runsResponse.data.workflow_runs.filter(
(run) => run.conclusion === "failure"
const workflowRuns = await ctx.octokit.paginate(
ctx.octokit.rest.actions.listWorkflowRunsForRepo,
{
owner: ctx.owner,
repo: ctx.name,
check_suite_id,
per_page: 100,
}
);
const failedRuns = workflowRuns.filter((run) => run.conclusion === "failure");
if (failedRuns.length === 0) {
return {
check_suite_id,
@@ -34,14 +35,14 @@ export const GetCheckSuiteLogsTool = tool({
// get logs for each failed run
const logsForRuns = await Promise.all(
failedRuns.map(async (run) => {
const jobsResponse = await ctx.octokit.rest.actions.listJobsForWorkflowRun({
const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
owner: ctx.owner,
repo: ctx.name,
run_id: run.id,
});
const jobLogs = await Promise.all(
jobsResponse.data.jobs.map(async (job) => {
jobs.map(async (job) => {
try {
const logsResponse = await ctx.octokit.rest.actions.downloadJobLogsForWorkflowRun({
owner: ctx.owner,
+43
View File
@@ -0,0 +1,43 @@
import { execSync } from "node:child_process";
import { type } from "arktype";
import { contextualize, tool } from "./shared.ts";
export const ListFiles = type({
path: type.string
.describe("The path to list files from (defaults to current directory)")
.default("."),
});
export const ListFilesTool = tool({
name: "list_files",
description:
"List files in the repository using git ls-files. Useful for discovering the file structure and locating files.",
parameters: ListFiles,
execute: contextualize(async ({ path }) => {
try {
// Use git ls-files to list tracked files
// This respects .gitignore and gives a clean list of source files
const command = path === "." ? "git ls-files" : `git ls-files ${path}`;
const output = execSync(command, { encoding: "utf-8" });
const files = output.split("\n").filter((f) => f.trim() !== "");
if (files.length === 0) {
// Fallback for non-git environments or untracked files
const findCmd = `find ${path} -maxdepth 3 -not -path '*/.*' -type f`;
const findOutput = execSync(findCmd, { encoding: "utf-8" });
return {
files: findOutput.split("\n").filter((f) => f.trim() !== ""),
method: "find",
};
}
return { files, method: "git" };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
error: `Failed to list files: ${errorMessage}`,
hint: "Try using a specific path if the repository root is not the current directory.",
};
}
}),
});
+2 -1
View File
@@ -36,7 +36,8 @@ export const PullRequestInfoTool = tool({
execSync(`git fetch origin ${headBranch}`, { stdio: "inherit" });
log.info(`Checking out PR branch: origin/${headBranch}`);
execSync(`git checkout origin/${headBranch}`, { stdio: "inherit" });
// check out a local branch tracking the remote branch so we can push changes
execSync(`git checkout -B ${headBranch} origin/${headBranch}`, { stdio: "inherit" });
return {
number: data.number,
+76
View File
@@ -0,0 +1,76 @@
import { type } from "arktype";
import { contextualize, tool } from "./shared.ts";
export const GetReviewComments = type({
pull_number: type.number.describe("The pull request number"),
review_id: type.number.describe("The review ID to get comments for"),
});
export const GetReviewCommentsTool = tool({
name: "get_review_comments",
description:
"Get all review comments for a specific pull request review. Returns line-by-line comments that were left on specific code locations.",
parameters: GetReviewComments,
execute: contextualize(async ({ pull_number, review_id }, ctx) => {
const comments = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listCommentsForReview, {
owner: ctx.owner,
repo: ctx.name,
pull_number,
review_id,
});
return {
review_id,
pull_number,
comments: comments.map((comment) => ({
id: comment.id,
body: comment.body,
path: comment.path,
line: comment.line,
side: comment.side,
start_line: comment.start_line,
start_side: comment.start_side,
user: typeof comment.user === "string" ? comment.user : comment.user?.login,
created_at: comment.created_at,
updated_at: comment.updated_at,
html_url: comment.html_url,
in_reply_to_id: comment.in_reply_to_id,
diff_hunk: comment.diff_hunk,
reactions: comment.reactions,
})),
count: comments.length,
};
}),
});
export const ListPullRequestReviews = type({
pull_number: type.number.describe("The pull request number to list reviews for"),
});
export const ListPullRequestReviewsTool = tool({
name: "list_pull_request_reviews",
description:
"List all reviews for a pull request. Returns all reviews including approvals, request changes, and comments.",
parameters: ListPullRequestReviews,
execute: contextualize(async ({ pull_number }, ctx) => {
const reviews = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listReviews, {
owner: ctx.owner,
repo: ctx.name,
pull_number,
});
return {
pull_number,
reviews: reviews.map((review) => ({
id: review.id,
body: review.body,
state: review.state,
user: review.user?.login,
commit_id: review.commit_id,
submitted_at: review.submitted_at,
html_url: review.html_url,
})),
count: reviews.length,
};
}),
});
+5
View File
@@ -6,10 +6,12 @@ import {
EditCommentTool,
UpdateWorkingCommentTool,
} from "./comment.ts";
// import { ListFilesTool } from "./files.ts";
import { IssueTool } from "./issue.ts";
import { PullRequestTool } from "./pr.ts";
import { PullRequestInfoTool } from "./prInfo.ts";
import { ReviewTool } from "./review.ts";
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
import { SelectModeTool } from "./selectMode.ts";
import { addTools } from "./shared.ts";
@@ -25,9 +27,12 @@ addTools(server, [
CreateWorkingCommentTool,
UpdateWorkingCommentTool,
IssueTool,
// ListFilesTool,
PullRequestTool,
ReviewTool,
PullRequestInfoTool,
GetReviewCommentsTool,
ListPullRequestReviewsTool,
GetCheckSuiteLogsTool,
]);