From 7cc3cff20b96b82c16506df442776f15b2f18066 Mon Sep 17 00:00:00 2001 From: km-anthropic Date: Fri, 1 Aug 2025 13:54:35 -0700 Subject: [PATCH] feat: add MCP servers to agent mode - Update agent mode to use prepareMcpConfig instead of empty config - Update prepareMcpConfig to accept both ParsedGitHubContext and AutomationContext - Add type guards for context-specific properties (isPR, entityNumber) - This enables bot actors to use full MCP capabilities via workflow_dispatch --- src/mcp/install-mcp-server.ts | 8 ++-- src/modes/agent/index.ts | 76 ++++++++++++++++------------------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 83ba5f6..23e47a5 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -1,6 +1,6 @@ import * as core from "@actions/core"; import { GITHUB_API_URL, GITHUB_SERVER_URL } from "../github/api/config"; -import type { ParsedGitHubContext } from "../github/context"; +import type { GitHubContext } from "../github/context"; import { Octokit } from "@octokit/rest"; type PrepareConfigParams = { @@ -12,7 +12,7 @@ type PrepareConfigParams = { additionalMcpConfig?: string; claudeCommentId?: string; allowedTools: string[]; - context: ParsedGitHubContext; + context: GitHubContext; }; async function checkActionsReadPermission( @@ -115,7 +115,7 @@ export async function prepareMcpConfig( const hasActionsReadPermission = context.inputs.additionalPermissions.get("actions") === "read"; - if (context.isPR && hasActionsReadPermission) { + if ('isPR' in context && context.isPR && hasActionsReadPermission) { // Verify the token actually has actions:read permission const actuallyHasPermission = await checkActionsReadPermission( process.env.ACTIONS_TOKEN || "", @@ -141,7 +141,7 @@ export async function prepareMcpConfig( GITHUB_TOKEN: process.env.ACTIONS_TOKEN, REPO_OWNER: owner, REPO_NAME: repo, - PR_NUMBER: context.entityNumber?.toString() || "", + PR_NUMBER: 'entityNumber' in context ? context.entityNumber?.toString() || "" : "", RUNNER_TEMP: process.env.RUNNER_TEMP || "/tmp", }, }; diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index 15a8d0c..0cd7db6 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -1,7 +1,8 @@ import * as core from "@actions/core"; -import { mkdir, writeFile } from "fs/promises"; import type { Mode, ModeOptions, ModeResult } from "../types"; import { isAutomationContext } from "../../github/context"; +import type { PreparedContext } from "../../create-prompt/types"; +import { prepareMcpConfig } from "../../mcp/install-mcp-server"; /** * Agent mode implementation. @@ -39,27 +40,10 @@ export const agentMode: Mode = { return false; }, - async prepare({ context }: ModeOptions): Promise { + async prepare({ context, githubToken }: ModeOptions): Promise { // Agent mode handles automation events (workflow_dispatch, schedule) only - // Create prompt directory - await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, { - recursive: true, - }); - - // Write the prompt file - the base action requires a prompt_file parameter, - // so we must create this file even though agent mode typically uses - // override_prompt or direct_prompt. If neither is provided, we write - // a minimal prompt with just the repository information. - const promptContent = - context.inputs.overridePrompt || - context.inputs.directPrompt || - `Repository: ${context.repository.owner}/${context.repository.repo}`; - - await writeFile( - `${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`, - promptContent, - ); + // Agent mode doesn't need to create prompt files here - handled by createPrompt // Export tool environment variables for agent mode const baseTools = [ @@ -80,29 +64,25 @@ export const agentMode: Mode = { ...context.inputs.disallowedTools, ]; - core.exportVariable("ALLOWED_TOOLS", allowedTools.join(",")); - core.exportVariable("DISALLOWED_TOOLS", disallowedTools.join(",")); + // Export as INPUT_ prefixed variables for the base action + core.exportVariable("INPUT_ALLOWED_TOOLS", allowedTools.join(",")); + core.exportVariable("INPUT_DISALLOWED_TOOLS", disallowedTools.join(",")); - // Agent mode uses a minimal MCP configuration - // We don't need comment servers or PR-specific tools for automation - const mcpConfig: any = { - mcpServers: {}, - }; - - // Add user-provided additional MCP config if any + // Get MCP configuration using the same setup as other modes const additionalMcpConfig = process.env.MCP_CONFIG || ""; - if (additionalMcpConfig.trim()) { - try { - const additional = JSON.parse(additionalMcpConfig); - if (additional && typeof additional === "object") { - Object.assign(mcpConfig, additional); - } - } catch (error) { - core.warning(`Failed to parse additional MCP config: ${error}`); - } - } + const mcpConfig = await prepareMcpConfig({ + githubToken, + owner: context.repository.owner, + repo: context.repository.repo, + branch: "", // Agent mode doesn't use branches + baseBranch: "", + additionalMcpConfig, + claudeCommentId: undefined, // Agent mode doesn't track comments + allowedTools: [...baseTools, ...context.inputs.allowedTools], + context, + }); - core.setOutput("mcp_config", JSON.stringify(mcpConfig)); + core.setOutput("mcp_config", mcpConfig); return { commentId: undefined, @@ -111,7 +91,21 @@ export const agentMode: Mode = { currentBranch: "", claudeBranch: undefined, }, - mcpConfig: JSON.stringify(mcpConfig), + mcpConfig: mcpConfig, }; }, + + generatePrompt(context: PreparedContext): string { + // Agent mode uses override or direct prompt, no GitHub data needed + if (context.overridePrompt) { + return context.overridePrompt; + } + + if (context.directPrompt) { + return context.directPrompt; + } + + // Minimal fallback - repository is a string in PreparedContext + return `Repository: ${context.repository}`; + }, };