mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
fix: merge multiple --mcp-config flags and support --allowed-tools parsing (#748)
* fix: merge multiple --mcp-config flags instead of overwriting When users provide their own --mcp-config in claude_args, the action's built-in MCP servers (github_comment, github_ci, etc.) were being lost because multiple --mcp-config flags were overwriting each other. This fix: - Adds mcp-config to ACCUMULATING_FLAGS to collect all values - Changes delimiter to null character to avoid conflicts with JSON - Adds mergeMcpConfigs() to combine mcpServers objects from multiple configs - Merges inline JSON configs while preserving file path configs Fixes #745 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com> * fix: support hyphenated --allowed-tools flag and multiple values The --allowed-tools flag was not being parsed correctly when: 1. Using the hyphenated form (--allowed-tools) instead of camelCase (--allowedTools) 2. Passing multiple space-separated values after a single flag (e.g., --allowed-tools "Tool1" "Tool2" "Tool3") This fix: - Adds hyphenated variants (allowed-tools, disallowed-tools) to ACCUMULATING_FLAGS - Updates parsing to consume all consecutive non-flag values for accumulating flags - Merges values from both camelCase and hyphenated variants Fixes #746 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ashwin Bhat <ashwin-ant@users.noreply.github.com>
This commit is contained in:
@@ -108,6 +108,48 @@ describe("parseSdkOptions", () => {
|
||||
expect(result.sdkOptions.extraArgs?.["allowedTools"]).toBeUndefined();
|
||||
expect(result.sdkOptions.extraArgs?.["model"]).toBe("claude-3-5-sonnet");
|
||||
});
|
||||
|
||||
test("should handle hyphenated --allowed-tools flag", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: '--allowed-tools "Edit,Read,Write"',
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.allowedTools).toEqual(["Edit", "Read", "Write"]);
|
||||
expect(result.sdkOptions.extraArgs?.["allowed-tools"]).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should accumulate multiple --allowed-tools flags (hyphenated)", () => {
|
||||
// This is the exact scenario from issue #746
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs:
|
||||
'--allowed-tools "Bash(git log:*)" "Bash(git diff:*)" "Bash(git fetch:*)" "Bash(gh pr:*)"',
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.allowedTools).toEqual([
|
||||
"Bash(git log:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git fetch:*)",
|
||||
"Bash(gh pr:*)",
|
||||
]);
|
||||
});
|
||||
|
||||
test("should handle mixed camelCase and hyphenated allowedTools flags", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: '--allowedTools "Edit,Read" --allowed-tools "Write,Glob"',
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
// Both should be merged - note: order depends on which key is found first
|
||||
expect(result.sdkOptions.allowedTools).toContain("Edit");
|
||||
expect(result.sdkOptions.allowedTools).toContain("Read");
|
||||
expect(result.sdkOptions.allowedTools).toContain("Write");
|
||||
expect(result.sdkOptions.allowedTools).toContain("Glob");
|
||||
});
|
||||
});
|
||||
|
||||
describe("disallowedTools merging", () => {
|
||||
@@ -134,19 +176,129 @@ describe("parseSdkOptions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("other extraArgs passthrough", () => {
|
||||
test("should pass through mcp-config in extraArgs", () => {
|
||||
describe("mcp-config merging", () => {
|
||||
test("should pass through single mcp-config in extraArgs", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{}}' --allowedTools "Edit"`,
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{"command":"cmd1"}}}'`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(
|
||||
'{"mcpServers":{}}',
|
||||
'{"mcpServers":{"server1":{"command":"cmd1"}}}',
|
||||
);
|
||||
});
|
||||
|
||||
test("should merge multiple mcp-config flags with inline JSON", () => {
|
||||
// Simulates action prepending its config, then user providing their own
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node","args":["server.js"]}}}' --mcp-config '{"mcpServers":{"user_server":{"command":"custom","args":["run"]}}}'`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
const mcpConfig = JSON.parse(
|
||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
||||
);
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("user_server");
|
||||
expect(mcpConfig.mcpServers.github_comment.command).toBe("node");
|
||||
expect(mcpConfig.mcpServers.user_server.command).toBe("custom");
|
||||
});
|
||||
|
||||
test("should merge three mcp-config flags", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{"command":"cmd1"}}}' --mcp-config '{"mcpServers":{"server2":{"command":"cmd2"}}}' --mcp-config '{"mcpServers":{"server3":{"command":"cmd3"}}}'`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
const mcpConfig = JSON.parse(
|
||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
||||
);
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("server1");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("server2");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("server3");
|
||||
});
|
||||
|
||||
test("should handle mcp-config file path when no inline JSON exists", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config /tmp/user-mcp-config.json`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(
|
||||
"/tmp/user-mcp-config.json",
|
||||
);
|
||||
});
|
||||
|
||||
test("should merge inline JSON configs when file path is also present", () => {
|
||||
// When action provides inline JSON and user provides a file path,
|
||||
// the inline JSON configs should be merged (file paths cannot be merged at parse time)
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node"}}}' --mcp-config '{"mcpServers":{"github_ci":{"command":"node"}}}' --mcp-config /tmp/user-config.json`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
// The inline JSON configs should be merged
|
||||
const mcpConfig = JSON.parse(
|
||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
||||
);
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("github_ci");
|
||||
});
|
||||
|
||||
test("should handle mcp-config with other flags", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '{"mcpServers":{"server1":{}}}' --model claude-3-5-sonnet --mcp-config '{"mcpServers":{"server2":{}}}'`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
const mcpConfig = JSON.parse(
|
||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
||||
);
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("server1");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("server2");
|
||||
expect(result.sdkOptions.extraArgs?.["model"]).toBe("claude-3-5-sonnet");
|
||||
});
|
||||
|
||||
test("should handle real-world scenario: action config + user config", () => {
|
||||
// This is the exact scenario from the bug report
|
||||
const actionConfig = JSON.stringify({
|
||||
mcpServers: {
|
||||
github_comment: {
|
||||
command: "node",
|
||||
args: ["github-comment-server.js"],
|
||||
},
|
||||
github_ci: { command: "node", args: ["github-ci-server.js"] },
|
||||
},
|
||||
});
|
||||
const userConfig = JSON.stringify({
|
||||
mcpServers: {
|
||||
my_custom_server: { command: "python", args: ["server.py"] },
|
||||
},
|
||||
});
|
||||
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--mcp-config '${actionConfig}' --mcp-config '${userConfig}'`,
|
||||
};
|
||||
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
const mcpConfig = JSON.parse(
|
||||
result.sdkOptions.extraArgs?.["mcp-config"] as string,
|
||||
);
|
||||
// All servers should be present
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("github_comment");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("github_ci");
|
||||
expect(mcpConfig.mcpServers).toHaveProperty("my_custom_server");
|
||||
});
|
||||
});
|
||||
|
||||
describe("other extraArgs passthrough", () => {
|
||||
test("should pass through json-schema in extraArgs", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: `--json-schema '{"type":"object"}'`,
|
||||
|
||||
Reference in New Issue
Block a user