Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c3fd93f9 | |||
| 1c678f6ef8 | |||
| 23c18154ed |
+8
-2
@@ -2,7 +2,7 @@ import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|||||||
import packageJson from "../package.json" with { type: "json" };
|
import packageJson from "../package.json" with { type: "json" };
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import { agent, installFromNpmTarball, setupProcessAgentEnv } from "./shared.ts";
|
import { agent, createAgentEnv, installFromNpmTarball, setupProcessAgentEnv } from "./shared.ts";
|
||||||
|
|
||||||
export const claude = agent({
|
export const claude = agent({
|
||||||
name: "claude",
|
name: "claude",
|
||||||
@@ -15,7 +15,12 @@ export const claude = agent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath }) => {
|
run: async ({ payload, mcpServers, apiKey, cliPath }) => {
|
||||||
setupProcessAgentEnv({ ANTHROPIC_API_KEY: apiKey });
|
setupProcessAgentEnv({
|
||||||
|
// ANTHROPIC_API_KEY: apiKey
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete process.env.ANTHROPIC_API_KEY to ensure it's not used by the SDK
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
|
||||||
const prompt = addInstructions(payload);
|
const prompt = addInstructions(payload);
|
||||||
console.log(prompt);
|
console.log(prompt);
|
||||||
@@ -26,6 +31,7 @@ export const claude = agent({
|
|||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
mcpServers,
|
mcpServers,
|
||||||
pathToClaudeCodeExecutable: cliPath,
|
pathToClaudeCodeExecutable: cliPath,
|
||||||
|
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -83859,7 +83859,7 @@ function query({
|
|||||||
// package.json
|
// package.json
|
||||||
var package_default = {
|
var package_default = {
|
||||||
name: "@pullfrog/action",
|
name: "@pullfrog/action",
|
||||||
version: "0.0.114",
|
version: "0.0.116",
|
||||||
type: "module",
|
type: "module",
|
||||||
files: [
|
files: [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -92783,7 +92783,10 @@ var claude = agent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath }) => {
|
run: async ({ payload, mcpServers, apiKey, cliPath }) => {
|
||||||
setupProcessAgentEnv({ ANTHROPIC_API_KEY: apiKey });
|
setupProcessAgentEnv({
|
||||||
|
// ANTHROPIC_API_KEY: apiKey
|
||||||
|
});
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
const prompt = addInstructions(payload);
|
const prompt = addInstructions(payload);
|
||||||
console.log(prompt);
|
console.log(prompt);
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
@@ -92791,7 +92794,8 @@ var claude = agent({
|
|||||||
options: {
|
options: {
|
||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
mcpServers,
|
mcpServers,
|
||||||
pathToClaudeCodeExecutable: cliPath
|
pathToClaudeCodeExecutable: cliPath,
|
||||||
|
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for await (const message of queryInstance) {
|
for await (const message of queryInstance) {
|
||||||
@@ -121510,26 +121514,20 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes
|
|||||||
);
|
);
|
||||||
|
|
||||||
// mcp/shared.ts
|
// mcp/shared.ts
|
||||||
function getPayload() {
|
var mcpInitContext;
|
||||||
const payloadEnv = process.env.PULLFROG_PAYLOAD;
|
function initMcpContext(state) {
|
||||||
if (!payloadEnv) {
|
mcpInitContext = state;
|
||||||
throw new Error("PULLFROG_PAYLOAD environment variable is required");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(payloadEnv);
|
|
||||||
} catch (error41) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to parse PULLFROG_PAYLOAD: ${error41 instanceof Error ? error41.message : String(error41)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function getMcpContext() {
|
function getMcpContext() {
|
||||||
|
if (!mcpInitContext) {
|
||||||
|
throw new Error("MCP context not initialized. Call initializeMcpContext first.");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
...mcpInitContext,
|
||||||
...parseRepoContext(),
|
...parseRepoContext(),
|
||||||
octokit: new Octokit2({
|
octokit: new Octokit2({
|
||||||
auth: getGitHubInstallationToken()
|
auth: getGitHubInstallationToken()
|
||||||
}),
|
})
|
||||||
payload: getPayload()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var tool = (toolDef) => toolDef;
|
var tool = (toolDef) => toolDef;
|
||||||
@@ -122299,17 +122297,6 @@ var ListPullRequestReviewsTool = tool({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// mcp/selectMode.ts
|
// mcp/selectMode.ts
|
||||||
function getModes() {
|
|
||||||
const modesJson = process.env.PULLFROG_MODES;
|
|
||||||
if (modesJson) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(modesJson);
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
var SelectMode = type({
|
var SelectMode = type({
|
||||||
modeName: type.string.describe(
|
modeName: type.string.describe(
|
||||||
"the name of the mode to select (e.g., 'Plan', 'Build', 'Review', 'Prompt')"
|
"the name of the mode to select (e.g., 'Plan', 'Build', 'Review', 'Prompt')"
|
||||||
@@ -122319,19 +122306,13 @@ var SelectModeTool = tool({
|
|||||||
name: "select_mode",
|
name: "select_mode",
|
||||||
description: "Select a mode and get its detailed prompt instructions. Call this first to determine which mode to use based on the request.",
|
description: "Select a mode and get its detailed prompt instructions. Call this first to determine which mode to use based on the request.",
|
||||||
parameters: SelectMode,
|
parameters: SelectMode,
|
||||||
execute: contextualize(async ({ modeName }) => {
|
execute: contextualize(async ({ modeName }, ctx) => {
|
||||||
const allModes = getModes();
|
const selectedMode = ctx.modes.find((m) => m.name.toLowerCase() === modeName.toLowerCase());
|
||||||
if (allModes.length === 0) {
|
|
||||||
return {
|
|
||||||
error: "No modes available. Modes must be provided via PULLFROG_MODES environment variable."
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const selectedMode = allModes.find((m) => m.name.toLowerCase() === modeName.toLowerCase());
|
|
||||||
if (!selectedMode) {
|
if (!selectedMode) {
|
||||||
const availableModes = allModes.map((m) => m.name).join(", ");
|
const availableModes = ctx.modes.map((m) => m.name).join(", ");
|
||||||
return {
|
return {
|
||||||
error: `Mode "${modeName}" not found. Available modes: ${availableModes}`,
|
error: `Mode "${modeName}" not found. Available modes: ${availableModes}`,
|
||||||
availableModes: allModes.map((m) => ({ name: m.name, description: m.description }))
|
availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description }))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -122367,7 +122348,8 @@ async function findAvailablePort(startPort) {
|
|||||||
}
|
}
|
||||||
throw new Error(`Could not find available port starting from ${startPort}`);
|
throw new Error(`Could not find available port starting from ${startPort}`);
|
||||||
}
|
}
|
||||||
async function startMcpHttpServer() {
|
async function startMcpHttpServer(state) {
|
||||||
|
initMcpContext(state);
|
||||||
const server = new FastMCP({
|
const server = new FastMCP({
|
||||||
name: ghPullfrogMcpName,
|
name: ghPullfrogMcpName,
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
@@ -122711,12 +122693,6 @@ function parsePayload(inputs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function startMcpServer(ctx) {
|
async function startMcpServer(ctx) {
|
||||||
const repoContext = parseRepoContext();
|
|
||||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
|
||||||
const allModes = [...modes, ...ctx.payload.modes || []];
|
|
||||||
process.env.GITHUB_REPOSITORY = githubRepository;
|
|
||||||
process.env.PULLFROG_MODES = JSON.stringify(allModes);
|
|
||||||
process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload);
|
|
||||||
const runId = process.env.GITHUB_RUN_ID;
|
const runId = process.env.GITHUB_RUN_ID;
|
||||||
if (runId) {
|
if (runId) {
|
||||||
const workflowRunInfo = await fetchWorkflowRunInfo(runId);
|
const workflowRunInfo = await fetchWorkflowRunInfo(runId);
|
||||||
@@ -122725,7 +122701,8 @@ async function startMcpServer(ctx) {
|
|||||||
log.info(`\u{1F4DD} Using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
|
log.info(`\u{1F4DD} Using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { url: url2, close } = await startMcpHttpServer();
|
const allModes = [...modes, ...ctx.payload.modes || []];
|
||||||
|
const { url: url2, close } = await startMcpHttpServer({ payload: ctx.payload, modes: allModes });
|
||||||
ctx.mcpServerUrl = url2;
|
ctx.mcpServerUrl = url2;
|
||||||
ctx.mcpServerClose = close;
|
ctx.mcpServerClose = close;
|
||||||
log.info(`\u{1F680} MCP server started at ${url2}`);
|
log.info(`\u{1F680} MCP server started at ${url2}`);
|
||||||
|
|||||||
@@ -271,17 +271,6 @@ function parsePayload(inputs: Inputs): Payload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startMcpServer(ctx: MainContext): Promise<void> {
|
async function startMcpServer(ctx: MainContext): Promise<void> {
|
||||||
// set environment variables for MCP server tools
|
|
||||||
const repoContext = parseRepoContext();
|
|
||||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
|
||||||
const allModes = [...modes, ...(ctx.payload.modes || [])];
|
|
||||||
|
|
||||||
process.env.GITHUB_REPOSITORY = githubRepository;
|
|
||||||
process.env.PULLFROG_MODES = JSON.stringify(allModes);
|
|
||||||
process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload);
|
|
||||||
|
|
||||||
// GITHUB_RUN_ID is already set in GitHub Actions, no need to set it here
|
|
||||||
|
|
||||||
// fetch the pre-created progress comment ID from the database
|
// fetch the pre-created progress comment ID from the database
|
||||||
// this must be set BEFORE starting the MCP server so comment.ts can read it
|
// this must be set BEFORE starting the MCP server so comment.ts can read it
|
||||||
const runId = process.env.GITHUB_RUN_ID;
|
const runId = process.env.GITHUB_RUN_ID;
|
||||||
@@ -293,8 +282,8 @@ async function startMcpServer(ctx: MainContext): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start MCP server after env vars are set (comment.ts reads PULLFROG_PROGRESS_COMMENT_ID at import time)
|
const allModes = [...modes, ...(ctx.payload.modes || [])];
|
||||||
const { url, close } = await startMcpHttpServer();
|
const { url, close } = await startMcpHttpServer({ payload: ctx.payload, modes: allModes });
|
||||||
ctx.mcpServerUrl = url;
|
ctx.mcpServerUrl = url;
|
||||||
ctx.mcpServerClose = close;
|
ctx.mcpServerClose = close;
|
||||||
log.info(`🚀 MCP server started at ${url}`);
|
log.info(`🚀 MCP server started at ${url}`);
|
||||||
|
|||||||
+4
-27
@@ -1,20 +1,6 @@
|
|||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import type { Mode } from "../modes.ts";
|
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
// Get modes from environment variable (set by createMcpConfigs)
|
|
||||||
function getModes(): Mode[] {
|
|
||||||
const modesJson = process.env.PULLFROG_MODES;
|
|
||||||
if (modesJson) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(modesJson);
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SelectMode = type({
|
export const SelectMode = type({
|
||||||
modeName: type.string.describe(
|
modeName: type.string.describe(
|
||||||
"the name of the mode to select (e.g., 'Plan', 'Build', 'Review', 'Prompt')"
|
"the name of the mode to select (e.g., 'Plan', 'Build', 'Review', 'Prompt')"
|
||||||
@@ -26,23 +12,14 @@ export const SelectModeTool = tool({
|
|||||||
description:
|
description:
|
||||||
"Select a mode and get its detailed prompt instructions. Call this first to determine which mode to use based on the request.",
|
"Select a mode and get its detailed prompt instructions. Call this first to determine which mode to use based on the request.",
|
||||||
parameters: SelectMode,
|
parameters: SelectMode,
|
||||||
execute: contextualize(async ({ modeName }) => {
|
execute: contextualize(async ({ modeName }, ctx) => {
|
||||||
const allModes = getModes();
|
const selectedMode = ctx.modes.find((m) => m.name.toLowerCase() === modeName.toLowerCase());
|
||||||
|
|
||||||
if (allModes.length === 0) {
|
|
||||||
return {
|
|
||||||
error:
|
|
||||||
"No modes available. Modes must be provided via PULLFROG_MODES environment variable.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedMode = allModes.find((m) => m.name.toLowerCase() === modeName.toLowerCase());
|
|
||||||
|
|
||||||
if (!selectedMode) {
|
if (!selectedMode) {
|
||||||
const availableModes = allModes.map((m) => m.name).join(", ");
|
const availableModes = ctx.modes.map((m) => m.name).join(", ");
|
||||||
return {
|
return {
|
||||||
error: `Mode "${modeName}" not found. Available modes: ${availableModes}`,
|
error: `Mode "${modeName}" not found. Available modes: ${availableModes}`,
|
||||||
availableModes: allModes.map((m) => ({ name: m.name, description: m.description })),
|
availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description })),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -20,7 +20,7 @@ import { PullRequestInfoTool } from "./prInfo.ts";
|
|||||||
import { ReviewTool } from "./review.ts";
|
import { ReviewTool } from "./review.ts";
|
||||||
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
|
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
|
||||||
import { SelectModeTool } from "./selectMode.ts";
|
import { SelectModeTool } from "./selectMode.ts";
|
||||||
import { addTools } from "./shared.ts";
|
import { addTools, initMcpContext, type McpInitContext } from "./shared.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an available port starting from the given port
|
* Find an available port starting from the given port
|
||||||
@@ -54,7 +54,11 @@ async function findAvailablePort(startPort: number): Promise<number> {
|
|||||||
/**
|
/**
|
||||||
* Start the MCP HTTP server and return the URL and close function
|
* Start the MCP HTTP server and return the URL and close function
|
||||||
*/
|
*/
|
||||||
export async function startMcpHttpServer(): Promise<{ url: string; close: () => Promise<void> }> {
|
export async function startMcpHttpServer(
|
||||||
|
state: McpInitContext
|
||||||
|
): Promise<{ url: string; close: () => Promise<void> }> {
|
||||||
|
initMcpContext(state);
|
||||||
|
|
||||||
const server = new FastMCP({
|
const server = new FastMCP({
|
||||||
name: ghPullfrogMcpName,
|
name: ghPullfrogMcpName,
|
||||||
version: "0.0.1",
|
version: "0.0.1",
|
||||||
|
|||||||
+24
-24
@@ -2,46 +2,38 @@ import { Octokit } from "@octokit/rest";
|
|||||||
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
||||||
import type { FastMCP, Tool } from "fastmcp";
|
import type { FastMCP, Tool } from "fastmcp";
|
||||||
import type { Payload } from "../external.ts";
|
import type { Payload } from "../external.ts";
|
||||||
|
import type { Mode } from "../modes.ts";
|
||||||
import { getGitHubInstallationToken, parseRepoContext, type RepoContext } from "../utils/github.ts";
|
import { getGitHubInstallationToken, parseRepoContext, type RepoContext } from "../utils/github.ts";
|
||||||
|
|
||||||
export interface ToolResult {
|
export interface McpInitContext {
|
||||||
content: {
|
payload: Payload;
|
||||||
type: "text";
|
modes: Mode[];
|
||||||
text: string;
|
|
||||||
}[];
|
|
||||||
isError?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPayload(): Payload {
|
let mcpInitContext: McpInitContext | undefined;
|
||||||
const payloadEnv = process.env.PULLFROG_PAYLOAD;
|
|
||||||
if (!payloadEnv) {
|
|
||||||
throw new Error("PULLFROG_PAYLOAD environment variable is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// this must be called on mcp server initialization
|
||||||
return JSON.parse(payloadEnv) as Payload;
|
export function initMcpContext(state: McpInitContext): void {
|
||||||
} catch (error) {
|
mcpInitContext = state;
|
||||||
throw new Error(
|
}
|
||||||
`Failed to parse PULLFROG_PAYLOAD: ${error instanceof Error ? error.message : String(error)}`
|
|
||||||
);
|
export interface McpContext extends McpInitContext, RepoContext {
|
||||||
}
|
octokit: Octokit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMcpContext(): McpContext {
|
export function getMcpContext(): McpContext {
|
||||||
|
if (!mcpInitContext) {
|
||||||
|
throw new Error("MCP context not initialized. Call initializeMcpContext first.");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
...mcpInitContext,
|
||||||
...parseRepoContext(),
|
...parseRepoContext(),
|
||||||
octokit: new Octokit({
|
octokit: new Octokit({
|
||||||
auth: getGitHubInstallationToken(),
|
auth: getGitHubInstallationToken(),
|
||||||
}),
|
}),
|
||||||
payload: getPayload(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface McpContext extends RepoContext {
|
|
||||||
octokit: Octokit;
|
|
||||||
payload: Payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
|
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
|
||||||
|
|
||||||
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
||||||
@@ -65,6 +57,14 @@ export const contextualize = <T>(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ToolResult {
|
||||||
|
content: {
|
||||||
|
type: "text";
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
isError?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const handleToolSuccess = (data: Record<string, any>): ToolResult => {
|
const handleToolSuccess = (data: Record<string, any>): ToolResult => {
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.114",
|
"version": "0.0.116",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user