anthropics / anthropics/claude-code-action

User-provided MCP config file paths are silently discarded when action prepends inline JSON

Abierto
#754 1 comentario 6 reacciones 0 asignados Ver en GitHub
bug mcp p2
Lenguaje dominante
TypeScript
Estrellas
8.9k
Forks
2.1k
Merge medio
3 d 9 h
PR fusionados (30 d)
10

Descripción

## Summary

When providing a `--mcp-config` flag with a **file path** in `claude_args`, the user's MCP servers are silently discarded because the action prepends its own inline JSON config. The `mergeMcpConfigs` function in `base-action/src/parse-sdk-options.ts` does not read file paths when inline JSON configs exist.

## Reproduction

### Workflow configuration

```yaml
- name: Setup GitHub MCP Server Config
run: |
mkdir -p /tmp/mcp-config
cat > /tmp/mcp-config/mcp-servers.json << EOF
{
"mcpServers": {
"my_custom_server": {
"command": "docker",
"args": ["run", "-i", "--rm", "my-mcp-server"],
"env": {}
}
}
}
EOF

- name: Run Claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Do something"
claude_args: |
--mcp-config /tmp/mcp-config/mcp-servers.json
--allowedTools "mcp__my_custom_server__some_tool"
```

### Expected behavior

The final MCP config should include both:
- The action's built-in servers (`github_comment`, `github_ci`, etc.)
- The user's custom server (`my_custom_server`)

### Actual behavior

Only the action's built-in servers are included. The user's file path is silently discarded, and `my_custom_server` is never available.

From the action logs:
```json
"extraArgs": {
"mcp-config": "{\"mcpServers\":{\"github_comment\":{...},\"github_ci\":{...}}}",
}
```

The user's `my_custom_server` is missing entirely.

## Root cause

In `base-action/src/parse-sdk-options.ts`, the `mergeMcpConfigs` function handles file paths incorrectly:

```typescript
function mergeMcpConfigs(configValues: string[]): string {
const merged: McpConfig = { mcpServers: {} };
let lastFilePath: string | null = null;

for (const config of configValues) {
const trimmed = config.trim();
if (!trimmed) continue;

if (trimmed.startsWith("{")) {
// Inline JSON - gets merged ✓
const parsed = JSON.parse(trimmed) as McpConfig;
Object.assign(merged.mcpServers!, parsed.mcpServers);
} else {
// File path - just stored, never read ✗
lastFilePath = trimmed;
}
}

// File path only returned if NO inline JSON exists
if (Object.keys(merged.mcpServers!).length === 0 && lastFilePath) {
return lastFilePath;
}

// File path is discarded here!
return JSON.stringify(merged);
}
```

The issue is that:
1. The action prepends inline JSON config for built-in servers
2. User provides a file path
3. File path is stored in `lastFilePath` but never read
4. Since inline JSON exists, the condition `Object.keys(merged.mcpServers!).length === 0` is false
5. The function returns only the merged inline JSON, discarding the file path

The code comments acknowledge this limitation but describe it as expected behavior:
> "If user passes a file path, they should ensure it includes all needed servers"

However, this is problematic because:
1. Users have no way to include the action's built-in servers in their file (they don't know the paths)
2. The file is silently discarded with no warning
3. The documentation doesn't mention this limitation

## Suggested fix

Read and merge file paths at parse time:

```typescript
import { readFileSync } from "fs";

function mergeMcpConfigs(configValues: string[]): string {
const merged: McpConfig = { mcpServers: {} };

for (const config of configValues) {
const trimmed = config.trim();
if (!trimmed) continue;

let configToMerge: McpConfig;

if (trimmed.startsWith("{")) {
// Inline JSON
configToMerge = JSON.parse(trimmed);
} else {
// File path - read and parse
try {
const fileContent = readFileSync(trimmed, "utf-8");
configToMerge = JSON.parse(fileContent);
} catch (error) {
console.warn(`Failed to read MCP config file: ${trimmed}`, error);
continue;
}
}

if (configToMerge.mcpServers) {
Object.assign(merged.mcpServers!, configToMerge.mcpServers);
}
}

return JSON.stringify(merged);
}
```

## Workaround

Until this is fixed, users must provide their MCP config as inline JSON instead of a file path:

```yaml
claude_args: |
--mcp-config '{"mcpServers":{"my_custom_server":{"command":"docker","args":["run","-i","--rm","my-mcp-server"],"env":{}}}}'
```

This is cumbersome for complex configurations and doesn't match the documented behavior.

## Environment

- `anthropics/claude-code-action@v1`
- Using `claude_args` with `--mcp-config` pointing to a file path

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.