anthropics / anthropics/claude-code-action

--mcp-config file path in claude_args is silently dropped when action's inline JSON config is present

Open
#1,004 1 comment 6 reactions 0 assignees View on GitHub
bug mcp p2
Dominant language
TypeScript
Stars
8.9k
Forks
2.1k
Avg merge
3d 9h
Merged PRs (30d)
10

Description

## Summary

When a user passes `--mcp-config /path/to/file.json` inside `claude_args`, the file path is silently dropped and the custom MCP servers never start. The agent appears to have the tools available (they show up in `allowedTools`) but the MCP server is absent from `mcp_servers` entirely — so the tools are uncallable.

## Root Cause (traced to source)

In `base-action/src/parse-sdk-options.ts`, `mergeMcpConfigs` has two branches:

- **Inline JSON** (starts with `{`) → parsed and merged into `merged.mcpServers`
- **File path** → stored as `lastFilePath`, returned only if `merged.mcpServers` is empty

The action always prepends its own built-in servers (e.g. `github_comment`) as **inline JSON** before user `claude_args` are processed. This means `merged.mcpServers` is never empty by the time user's file path is evaluated. The condition on line 69:

```typescript
if (Object.keys(merged.mcpServers!).length === 0 && lastFilePath) {
return lastFilePath;
}
```

is never true, so the function falls through to `return JSON.stringify(merged)` — returning only the action's own inline-JSON servers, with the user's file path silently lost.

The code comment acknowledges this but proposes an impossible workaround:

> _"If user passes a file path, they should ensure it includes all needed servers."_

This is not feasible since users cannot know the action's internal server configuration at workflow-write time, and it changes between action versions.

Note also that the function-level docstring contradicts the implementation:

> _"For file paths, they are kept as-is (user's file takes precedence and is used last)."_

## Evidence From CI Logs

Session init (`extraArgs["mcp-config"]`) shows only the action's built-in server — the user's file path is absent:

```json
"mcp-config": "{\"mcpServers\":{\"github_comment\":{...}}}"
```

`mcp_servers` at connection time: custom server is not listed as `FAILED` or `needs-auth` — it is completely absent, confirming the config referencing it was never passed to the SDK.

The `allowedTools` array does include the custom tools (because `--allowedTools` is a separate flag that passes through verbatim), creating a misleading state: tools appear allowed but the server never started.

## Steps to Reproduce

1. Write a custom MCP config to a temp file in a workflow step:
```yaml
- name: Write MCP config
run: |
cat > /tmp/my-mcp-config.json <<'EOF'
{"mcpServers":{"my_server":{"command":"uvx","args":["..."]}}}
EOF
```
2. Pass it via `claude_args`:
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: --mcp-config /tmp/my-mcp-config.json --allowedTools "mcp__my_server__*"
```
3. Inspect session init logs — `my_server` will be absent from `mcp_servers`.

## Expected Behavior

The function docstring says:
> _"For file paths, they are kept as-is (user's file takes precedence and is used last)."_

Both the action's built-in inline JSON servers and the user's file-path server should be active in the session.

## Workaround

Pass the custom MCP config as **inline JSON** instead of a file path. Inline JSON is merged correctly:

```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: >-
--mcp-config '{"mcpServers":{"my_server":{"command":"uvx","args":["..."]}}}'
--allowedTools "mcp__my_server__*"
```

The "Write MCP config" step can be removed entirely when using this approach.

## Suggested Fix

Read the file at merge time — `existsSync` and `readFileSync` are already imported in `run.ts` and available in the runner environment. When a file path is encountered in `mergeMcpConfigs`, read and parse the file content and merge its `mcpServers` with the rest, rather than storing it as `lastFilePath` and conditionally returning it.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.