From d5fbc80b71be8b4554ae43a915e6a4bb2ee7044e Mon Sep 17 00:00:00 2001 From: km-anthropic Date: Mon, 11 Aug 2025 06:42:03 -0700 Subject: [PATCH] Fix MCP tool availability and shell escaping in tag mode Pass MCP config and allowed tools through claude_args to ensure tools like mcp__github_comment__update_claude_comment are properly available to Claude CLI. Key changes: - Tag mode outputs claude_args with MCP config (as JSON string) and allowed tools - Fixed shell escaping vulnerability when JSON contains single quotes - Agent mode passes through user-provided claude_args unchanged - Re-added mcp_config input for users to provide custom MCP servers - Cleaned up misleading comments and unused file operations - Clarified test workflow is for fork testing Security fix: Properly escape single quotes in MCP config JSON to prevent shell injection vulnerabilities. Co-Authored-By: Claude --- .github/workflows/claude-test.yml | 38 ++++++++++++++++++++++++ action.yml | 8 +++++- src/entrypoints/prepare.ts | 7 +---- src/modes/agent/index.ts | 4 ++- src/modes/tag/index.ts | 48 ++++++++++++++++++++++++++++++- 5 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/claude-test.yml diff --git a/.github/workflows/claude-test.yml b/.github/workflows/claude-test.yml new file mode 100644 index 0000000..5949daf --- /dev/null +++ b/.github/workflows/claude-test.yml @@ -0,0 +1,38 @@ +# Test workflow for km-anthropic fork (v1-dev branch) +# This tests the fork implementation, not the main repo +name: Claude Code (Fork Test) + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && ( + contains(github.event.issue.body, '@claude') || + contains(github.event.issue.title, '@claude') + )) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write # Required for OIDC token exchange + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run Claude Code + uses: km-anthropic/claude-code-action@v1-dev + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/action.yml b/action.yml index d2924ac..25ac686 100644 --- a/action.yml +++ b/action.yml @@ -65,6 +65,10 @@ inputs: description: "Additional arguments to pass directly to Claude CLI" required: false default: "" + mcp_config: + description: "Additional MCP configuration (JSON string) that merges with built-in GitHub MCP servers" + required: false + default: "" additional_permissions: description: "Additional GitHub permissions to request (e.g., 'actions: read')" required: false @@ -124,6 +128,8 @@ runs: DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} + CLAUDE_ARGS: ${{ inputs.claude_args }} + MCP_CONFIG: ${{ inputs.mcp_config }} - name: Install Base Action Dependencies if: steps.prepare.outputs.contains_trigger == 'true' @@ -160,7 +166,7 @@ runs: INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt INPUT_SETTINGS: ${{ inputs.settings }} INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} - INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.mcp_config_file && format('--mcp-config {0} {1}', steps.prepare.outputs.mcp_config_file, inputs.claude_args) || inputs.claude_args }} + INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }} INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands # Model configuration diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index 8682d7d..d485f82 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -6,7 +6,6 @@ */ import * as core from "@actions/core"; -import { writeFile } from "fs/promises"; import { setupGitHubToken } from "../github/token"; import { checkWritePermissions } from "../github/validation/permissions"; import { createOctokit } from "../github/api/client"; @@ -58,11 +57,7 @@ async function run() { githubToken, }); - // Write MCP config to a file and set the file path as output - const mcpConfigPath = `${process.env.RUNNER_TEMP}/claude-mcp-config.json`; - await writeFile(mcpConfigPath, result.mcpConfig); - core.setOutput("mcp_config", result.mcpConfig); - core.setOutput("mcp_config_file", mcpConfigPath); + // MCP config is handled by individual modes (tag/agent) and included in their claude_args output // Step 6: Get system prompt from mode if available if (mode.getSystemPrompt) { diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index dc64692..34e7149 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -79,7 +79,9 @@ export const agentMode: Mode = { } } - core.setOutput("mcp_config", JSON.stringify(mcpConfig)); + // Agent mode: pass through user's claude_args without modification + const userClaudeArgs = process.env.CLAUDE_ARGS || ""; + core.setOutput("claude_args", userClaudeArgs); return { commentId: undefined, diff --git a/src/modes/tag/index.ts b/src/modes/tag/index.ts index 73e274e..4e8e277 100644 --- a/src/modes/tag/index.ts +++ b/src/modes/tag/index.ts @@ -114,7 +114,53 @@ export const tagMode: Mode = { context, }); - core.setOutput("mcp_config", mcpConfig); + // Don't output mcp_config separately anymore - include in claude_args + + // Build claude_args for tag mode with required tools + // Tag mode REQUIRES these tools to function properly + const tagModeTools = [ + "Edit", + "MultiEdit", + "Glob", + "Grep", + "LS", + "Read", + "Write", + "mcp__github_comment__update_claude_comment", + ]; + + // Add git commands when not using commit signing + if (!context.inputs.useCommitSigning) { + tagModeTools.push( + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git status:*)", + "Bash(git diff:*)", + "Bash(git log:*)", + "Bash(git rm:*)" + ); + } else { + // When using commit signing, use MCP file ops tools + tagModeTools.push( + "mcp__github_file_ops__commit_files", + "mcp__github_file_ops__delete_files" + ); + } + + const userClaudeArgs = process.env.CLAUDE_ARGS || ""; + + // Build complete claude_args with MCP config (as JSON string), tools, and user args + // Note: Once Claude supports multiple --mcp-config flags, we can pass as file path + // Escape single quotes in JSON to prevent shell injection + const escapedMcpConfig = mcpConfig.replace(/'/g, "'\\''"); + let claudeArgs = `--mcp-config '${escapedMcpConfig}' `; + claudeArgs += `--allowedTools "${tagModeTools.join(',')}" `; + if (userClaudeArgs) { + claudeArgs += userClaudeArgs; + } + + core.setOutput("claude_args", claudeArgs.trim()); return { commentId,