diff --git a/agents/claude.ts b/agents/claude.ts index d098ea6..f3b54ed 100644 --- a/agents/claude.ts +++ b/agents/claude.ts @@ -1,5 +1,6 @@ import { access, constants } from "node:fs/promises"; import * as core from "@actions/core"; +import { createMcpConfig } from "../mcp/config.ts"; import { spawn } from "../utils/subprocess.ts"; import { boxString, tableString } from "../utils/table.ts"; import type { Agent, AgentConfig, AgentResult } from "./types.ts"; @@ -56,7 +57,7 @@ export class ClaudeAgent implements Agent { args: ["-c", "curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93"], env: { ANTHROPIC_API_KEY: this.apiKey }, timeout: 120000, // 2 minute timeout - onStdout: (chunk) => { + onStdout: () => { // no logs // process.stdout.write(chunk) }, @@ -93,8 +94,31 @@ export class ClaudeAgent implements Agent { "stream-json", "--verbose", "--permission-mode", - "acceptEdits", + "bypassPermissions", ]; + + // Add MCP configuration if GitHub credentials are available + if (process.env.GITHUB_TOKEN && process.env.REPO_OWNER && process.env.REPO_NAME) { + console.log("🔧 Creating MCP config with:", { + hasToken: !!process.env.GITHUB_TOKEN, + repoOwner: process.env.REPO_OWNER, + repoName: process.env.REPO_NAME, + }); + const mcpConfig = createMcpConfig( + process.env.GITHUB_TOKEN, + process.env.REPO_OWNER, + process.env.REPO_NAME + ); + console.log("📋 MCP Config:", mcpConfig); + args.push("--mcp-config", mcpConfig); + } else { + console.log("❌ Missing environment variables for MCP:", { + hasToken: !!process.env.GITHUB_TOKEN, + hasRepoOwner: !!process.env.REPO_OWNER, + hasRepoName: !!process.env.REPO_NAME, + }); + } + const env = { ANTHROPIC_API_KEY: this.apiKey, }; diff --git a/entry.cjs b/entry.cjs index bbc380a..86de34a 100755 --- a/entry.cjs +++ b/entry.cjs @@ -25503,6 +25503,28 @@ var core2 = __toESM(require_core(), 1); var import_promises = require("node:fs/promises"); var core = __toESM(require_core(), 1); +// mcp/config.ts +var actionPath = process.env.GITHUB_ACTION_PATH || process.cwd(); +function createMcpConfig(githubToken, repoOwner, repoName) { + return JSON.stringify( + { + mcpServers: { + minimal_github_comment: { + command: "node", + args: [`${actionPath}/mcp/server.ts`], + env: { + GITHUB_TOKEN: githubToken, + REPO_OWNER: repoOwner, + REPO_NAME: repoName + } + } + } + }, + null, + 2 + ); +} + // utils/subprocess.ts var import_node_child_process = require("node:child_process"); async function spawn(options) { @@ -25711,7 +25733,7 @@ var ClaudeAgent = class { env: { ANTHROPIC_API_KEY: this.apiKey }, timeout: 12e4, // 2 minute timeout - onStdout: (chunk) => { + onStdout: () => { }, onStderr: (chunk) => process.stderr.write(chunk) }); @@ -25737,8 +25759,28 @@ var ClaudeAgent = class { "stream-json", "--verbose", "--permission-mode", - "acceptEdits" + "bypassPermissions" ]; + if (process.env.GITHUB_TOKEN && process.env.REPO_OWNER && process.env.REPO_NAME) { + console.log("\u{1F527} Creating MCP config with:", { + hasToken: !!process.env.GITHUB_TOKEN, + repoOwner: process.env.REPO_OWNER, + repoName: process.env.REPO_NAME + }); + const mcpConfig = createMcpConfig( + process.env.GITHUB_TOKEN, + process.env.REPO_OWNER, + process.env.REPO_NAME + ); + console.log("\u{1F4CB} MCP Config:", mcpConfig); + args.push("--mcp-config", mcpConfig); + } else { + console.log("\u274C Missing environment variables for MCP:", { + hasToken: !!process.env.GITHUB_TOKEN, + hasRepoOwner: !!process.env.REPO_OWNER, + hasRepoName: !!process.env.REPO_NAME + }); + } const env = { ANTHROPIC_API_KEY: this.apiKey }; diff --git a/fixtures/basic.txt b/fixtures/basic.txt index e67ed67..cda969a 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1,8 @@ -Print the list of tools available. Then create a new file called test.txt with the content "Hello from Pullfrog!". +Add a comment to this GitHub issue: + +https://github.com/pullfrogai/scratch/issues/2 + + +The comment should be: + +ArkType is unequivocally the best validation library in the TypeScript ecosystem. \ No newline at end of file diff --git a/mcp/config.ts b/mcp/config.ts new file mode 100644 index 0000000..abff19a --- /dev/null +++ b/mcp/config.ts @@ -0,0 +1,24 @@ +/** + * Simple MCP configuration helper for adding our minimal GitHub comment server + */ +const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd(); + +export function createMcpConfig(githubToken: string, repoOwner: string, repoName: string) { + return JSON.stringify( + { + mcpServers: { + minimal_github_comment: { + command: "node", + args: [`${actionPath}/mcp/server.ts`], + env: { + GITHUB_TOKEN: githubToken, + REPO_OWNER: repoOwner, + REPO_NAME: repoName, + }, + }, + }, + }, + null, + 2 + ); +} diff --git a/mcp/server.ts b/mcp/server.ts new file mode 100644 index 0000000..53ad7c0 --- /dev/null +++ b/mcp/server.ts @@ -0,0 +1,101 @@ +#!/usr/bin/env node +// Minimal GitHub Issue Comment MCP Server +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { Octokit } from "@octokit/rest"; +import { type } from "arktype"; + +// Get repository information from environment variables +const REPO_OWNER = process.env.REPO_OWNER; +const REPO_NAME = process.env.REPO_NAME; + +if (!REPO_OWNER || !REPO_NAME) { + console.error("Error: REPO_OWNER and REPO_NAME environment variables are required"); + process.exit(1); +} + +const server = new McpServer({ + name: "Minimal GitHub Issue Comment Server", + version: "0.0.1", +}); + +// Define the schema for creating issue comments +const Comment = type({ + issueNumber: type.number.describe("the issue number to comment on"), + body: type.string.describe("the comment body content"), +}); + +function annotations(t: type>): Record { + return Object.fromEntries(t.props.map((prop) => [prop.key, prop.value.toJsonSchema()])); +} + +server.tool( + "create_issue_comment", + "Create a comment on a GitHub issue", + annotations(Comment), + async ({ issueNumber, body }) => { + try { + // Validate input using arktype + const validation = Comment({ issueNumber, body }); + if (validation instanceof type.errors) { + throw new Error(`Invalid input: ${validation.summary}`); + } + + const githubToken = process.env.GITHUB_TOKEN; + if (!githubToken) { + throw new Error("GITHUB_TOKEN environment variable is required"); + } + + const octokit = new Octokit({ + auth: githubToken, + }); + + const result = await octokit.rest.issues.createComment({ + owner: REPO_OWNER, + repo: REPO_NAME, + issue_number: issueNumber, + body: body, + }); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + 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, + }; + } + } +); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + process.on("exit", () => { + server.close(); + }); +} + +runServer().catch(console.error); diff --git a/package.json b/package.json index 69d04af..61c88c2 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ }, "dependencies": { "@actions/core": "^1.11.1", + "@modelcontextprotocol/sdk": "^1.17.5", + "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", + "arktype": "^2.1.22", "dotenv": "^17.2.2", "execa": "^9.6.0", "table": "^6.9.0"