Nested ${VAR:-${HOME}...} expansion broken in .mcp.json args
Description
Claude Code's environment variable expansion in .mcp.json does not correctly handle nested ${...} inside ${VAR:-default} syntax. The parser appears to match the first } it encounters rather than the balanced/outer }, causing the remaining text to be appended as a literal suffix.
Reproduction
Given this .mcp.json entry (from an MCP plugin):
{
"my-server": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "${AI_TOOLS_MCP_DIR:-${HOME}/.ai-tools-mcp}:/root/.ai-tools-mcp:ro",
"some-image:latest"
]
}
}
With AI_TOOLS_MCP_DIR=/mnt/c/Users/username/.ai-tools-mcp set in the environment:
| | Volume mount source path |
|---|---|
| Expected | /mnt/c/Users/username/.ai-tools-mcp |
| Actual | /mnt/c/Users/username/.ai-tools-mcp/.ai-tools-mcp} |
The parser expands ${AI_TOOLS_MCP_DIR:-${HOME} (up to the first }), resolves the variable correctly, but then treats the remaining /.ai-tools-mcp} as literal text and appends it.
Evidence
Docker auto-creates missing volume mount source directories. After Claude Code starts MCP servers with the broken paths, the following directory structure appears on the host (owned by root, all empty):
/mnt/c/Users/username/.ai-tools-mcp/.ai-tools-mcp}/
├── certs/
├── documents/
└── vector-db/
The .ai-tools-mcp} directory name (with literal }) and its empty subdirectories exactly match the volume mount patterns in .mcp.json, confirming the expansion produced a broken path.
Impact
- All MCP servers using
${VAR:-${NESTED}/path}in their.mcp.jsonargs get broken volume mounts - Containers start with empty mount directories and fail to find credentials/config
- Servers that require credentials at startup (GitHub, Jira, Confluence, etc.) crash immediately
- Servers that can start without credentials (e.g., document-search) appear to work but have no data
- The failure is silent from the user's perspective —
/mcpjust shows "Failed to reconnect"
Workaround
Replacing ${AI_TOOLS_MCP_DIR:-${HOME}/.ai-tools-mcp} with ${AI_TOOLS_MCP_DIR} (no default value, no nesting) in .mcp.json fixes the issue. However, this requires the env var to always be set, and for plugins the fix is overwritten on updates.
Expected Behavior
${VAR:-default} expansion should handle nested ${...} in the default value by matching balanced braces, consistent with POSIX shell behavior. If nested expansion is not supported, the documentation for .mcp.json should note this limitation.
Environment
- Claude Code: 2.1.41
- OS: Linux (WSL2) 6.6.87.2-microsoft-standard-WSL2 x86_64
- Docker: Rancher Desktop (via WSL2 socket)
Related
- Plugin-side tracking issue: https://github.com/RenPSG/AI-Tools/issues/171
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗