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 <noreply@anthropic.com>
This commit is contained in:
km-anthropic
2025-08-11 06:42:03 -07:00
parent 5bdb1e4ae0
commit d5fbc80b71
5 changed files with 96 additions and 9 deletions

38
.github/workflows/claude-test.yml vendored Normal file
View File

@@ -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 }}

View File

@@ -65,6 +65,10 @@ inputs:
description: "Additional arguments to pass directly to Claude CLI" description: "Additional arguments to pass directly to Claude CLI"
required: false required: false
default: "" default: ""
mcp_config:
description: "Additional MCP configuration (JSON string) that merges with built-in GitHub MCP servers"
required: false
default: ""
additional_permissions: additional_permissions:
description: "Additional GitHub permissions to request (e.g., 'actions: read')" description: "Additional GitHub permissions to request (e.g., 'actions: read')"
required: false required: false
@@ -124,6 +128,8 @@ runs:
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
CLAUDE_ARGS: ${{ inputs.claude_args }}
MCP_CONFIG: ${{ inputs.mcp_config }}
- name: Install Base Action Dependencies - name: Install Base Action Dependencies
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'
@@ -160,7 +166,7 @@ runs:
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
INPUT_SETTINGS: ${{ inputs.settings }} INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} 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 INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
# Model configuration # Model configuration

View File

@@ -6,7 +6,6 @@
*/ */
import * as core from "@actions/core"; import * as core from "@actions/core";
import { writeFile } from "fs/promises";
import { setupGitHubToken } from "../github/token"; import { setupGitHubToken } from "../github/token";
import { checkWritePermissions } from "../github/validation/permissions"; import { checkWritePermissions } from "../github/validation/permissions";
import { createOctokit } from "../github/api/client"; import { createOctokit } from "../github/api/client";
@@ -58,11 +57,7 @@ async function run() {
githubToken, githubToken,
}); });
// Write MCP config to a file and set the file path as output // MCP config is handled by individual modes (tag/agent) and included in their claude_args 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);
// Step 6: Get system prompt from mode if available // Step 6: Get system prompt from mode if available
if (mode.getSystemPrompt) { if (mode.getSystemPrompt) {

View File

@@ -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 { return {
commentId: undefined, commentId: undefined,

View File

@@ -114,7 +114,53 @@ export const tagMode: Mode = {
context, 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 { return {
commentId, commentId,