Compare commits

...

1 Commits

Author SHA1 Message Date
David Blass c6c1210fa0 refactor tool implementation 2025-10-23 15:21:08 -04:00
6 changed files with 116 additions and 143 deletions
+8 -1
View File
@@ -165,8 +165,15 @@ export class ClaudeAgent implements Agent {
*/ */
function processJSONChunk(chunk: string, agent?: ClaudeAgent): void { function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
try { try {
// Skip debug lines that start with [DEBUG] or [debug]
const trimmedChunk = chunk.trim();
if (trimmedChunk.startsWith("[DEBUG]") || trimmedChunk.startsWith("[debug]")) {
console.log(chunk);
return;
}
console.log(chunk); console.log(chunk);
const parsedChunk = JSON.parse(chunk.trim()); const parsedChunk = JSON.parse(trimmedChunk);
switch (parsedChunk.type) { switch (parsedChunk.type) {
case "system": case "system":
+15 -41
View File
@@ -1,5 +1,5 @@
import { type } from "arktype"; import { type } from "arktype";
import { getMcpContext, tool } from "./shared.ts"; import { contextualize, tool } from "./shared.ts";
export const Comment = type({ export const Comment = type({
issueNumber: type.number.describe("the issue number to comment on"), issueNumber: type.number.describe("the issue number to comment on"),
@@ -10,45 +10,19 @@ export const CommentTool = tool({
name: "create_issue_comment", name: "create_issue_comment",
description: "Create a comment on a GitHub issue", description: "Create a comment on a GitHub issue",
parameters: Comment, parameters: Comment,
execute: async ({ issueNumber, body }) => { execute: contextualize(async ({ issueNumber, body }, ctx) => {
const ctx = getMcpContext(); const result = await ctx.octokit.rest.issues.createComment({
try { owner: ctx.owner,
const result = await ctx.octokit.rest.issues.createComment({ repo: ctx.name,
owner: ctx.owner, issue_number: issueNumber,
repo: ctx.name, body: body,
issue_number: issueNumber, });
body: body,
});
return { return {
content: [ success: true,
{ commentId: result.data.id,
type: "text", url: result.data.html_url,
text: JSON.stringify( body: result.data.body,
{ };
success: true, }),
commentId: result.data.id,
url: result.data.html_url,
body: result.data.body,
},
null,
2
),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error creating comment: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
}); });
+21 -49
View File
@@ -1,5 +1,5 @@
import { type } from "arktype"; import { type } from "arktype";
import { getMcpContext, tool } from "./shared.ts"; import { contextualize, tool } from "./shared.ts";
export const Issue = type({ export const Issue = type({
title: type.string.describe("the title of the issue"), title: type.string.describe("the title of the issue"),
@@ -18,53 +18,25 @@ export const IssueTool = tool({
name: "create_issue", name: "create_issue",
description: "Create a new GitHub issue", description: "Create a new GitHub issue",
parameters: Issue, parameters: Issue,
execute: async ({ title, body, labels, assignees }) => { execute: contextualize(async ({ title, body, labels, assignees }, ctx) => {
const ctx = getMcpContext(); const result = await ctx.octokit.rest.issues.create({
try { owner: ctx.owner,
const result = await ctx.octokit.rest.issues.create({ repo: ctx.name,
owner: ctx.owner, title: title,
repo: ctx.name, body: body,
title: title, labels: labels ?? [],
body: body, assignees: assignees ?? [],
labels: labels ?? [], });
assignees: assignees ?? [],
});
return { return {
content: [ success: true,
{ issueId: result.data.id,
type: "text", number: result.data.number,
text: JSON.stringify( url: result.data.html_url,
{ title: result.data.title,
success: true, state: result.data.state,
issueId: result.data.id, labels: result.data.labels?.map((label) => (typeof label === "string" ? label : label.name)),
number: result.data.number, assignees: result.data.assignees?.map((assignee) => assignee.login),
url: result.data.html_url, };
title: result.data.title, }),
state: result.data.state,
labels: result.data.labels?.map((label) =>
typeof label === "string" ? label : label.name
),
assignees: result.data.assignees?.map((assignee) => assignee.login),
},
null,
2
),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error creating issue: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
}); });
+25 -51
View File
@@ -1,6 +1,6 @@
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import { type } from "arktype"; import { type } from "arktype";
import { getMcpContext, tool } from "./shared.ts"; import { contextualize, tool } from "./shared.ts";
export const PullRequest = type({ export const PullRequest = type({
title: type.string.describe("the title of the pull request"), title: type.string.describe("the title of the pull request"),
@@ -12,57 +12,31 @@ export const PullRequestTool = tool({
name: "create_pull_request", name: "create_pull_request",
description: "Create a pull request from the current branch", description: "Create a pull request from the current branch",
parameters: PullRequest, parameters: PullRequest,
execute: async ({ title, body, base }) => { execute: contextualize(async ({ title, body, base }, ctx) => {
const ctx = getMcpContext(); // Get the current branch name
try { const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
// Get the current branch name encoding: "utf8",
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { }).trim();
encoding: "utf8",
}).trim();
console.log(`Current branch: ${currentBranch}`); console.log(`Current branch: ${currentBranch}`);
const result = await ctx.octokit.rest.pulls.create({ const result = await ctx.octokit.rest.pulls.create({
owner: ctx.owner, owner: ctx.owner,
repo: ctx.name, repo: ctx.name,
title: title, title: title,
body: body, body: body,
head: currentBranch, head: currentBranch,
base: base, base: base,
}); });
return { return {
content: [ success: true,
{ pullRequestId: result.data.id,
type: "text", number: result.data.number,
text: JSON.stringify( url: result.data.html_url,
{ title: result.data.title,
success: true, head: result.data.head.ref,
pullRequestId: result.data.id, base: result.data.base.ref,
number: result.data.number, };
url: result.data.html_url, }),
title: result.data.title,
head: result.data.head.ref,
base: result.data.base.ref,
},
null,
2
),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error creating pull request: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
}
},
}); });
+46
View File
@@ -4,6 +4,15 @@ import type { StandardSchemaV1 } from "@standard-schema/spec";
import type { FastMCP, Tool } from "fastmcp"; import type { FastMCP, Tool } from "fastmcp";
import { parseRepoContext, type RepoContext } from "../utils/github.ts"; import { parseRepoContext, type RepoContext } from "../utils/github.ts";
export interface ToolResult {
content: {
type: "text";
text: string;
}[];
error?: string;
isError?: boolean;
}
export const getMcpContext = cached((): McpContext => { export const getMcpContext = cached((): McpContext => {
const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN; const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN;
if (!githubInstallationToken) { if (!githubInstallationToken) {
@@ -30,3 +39,40 @@ export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
} }
return server; return server;
}; };
export const contextualize =
<T>(executor: (params: T, ctx: McpContext) => Promise<Record<string, any>>) =>
async (params: T): Promise<ToolResult> => {
try {
const ctx = getMcpContext();
const result = await executor(params, ctx);
return handleToolSuccess(result);
} catch (error) {
return handleToolError(error);
}
};
const handleToolSuccess = (data: Record<string, any>): ToolResult => {
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
};
const handleToolError = (error: unknown): ToolResult => {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
error: errorMessage,
isError: true,
};
};
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@pullfrog/action", "name": "@pullfrog/action",
"version": "0.0.53", "version": "0.0.54",
"type": "module", "type": "module",
"files": [ "files": [
"index.js", "index.js",