Plugin MCP server silently fails to spawn when env block references ${user_config.*}
Environment
- Claude Code version: tested on both 2.1.112 and 2.1.116
- OS: macOS (Darwin 25.3.0)
- Shell: zsh
What's happening
When a plugin's mcpServers configuration references ${user_config.<field>} values in the env block, Claude Code silently fails to spawn the MCP server at session start. The plugin is installed and enabled, claude plugin list --json shows the config loaded correctly, but claude mcp list never lists the server and /mcp inside a running session doesn't show it either. No error appears in the /plugin → Errors tab.
Swapping ${user_config.*} for shell-style ${VAR:-default} interpolation makes the plugin's MCP server spawn correctly with no other changes.
Minimal reproduction
plugin.json (broken):
{
"name": "my-plugin",
"version": "0.1.0",
"userConfig": {
"api_key": {
"type": "string",
"title": "API Key",
"required": true,
"sensitive": true
}
},
"mcpServers": {
"my-server": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/server.js"],
"env": {
"API_KEY": "${user_config.api_key}"
}
}
}
}
Install the plugin, fill in the required api_key at the install prompt, restart Claude Code. /mcp does NOT show my-server.
plugin.json (works):
{
"name": "my-plugin",
"version": "0.1.0",
"mcpServers": {
"my-server": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/server.js"],
"env": {
"API_KEY": "${API_KEY:-}"
}
}
}
}
Same plugin, shell env var interpolation, no userConfig. Server spawns correctly.
Steps to reproduce with a real plugin
/plugin marketplace add sherifButt/claude-image-tts-gen/plugin install claude-image-tts-gen@claude-image-tts-gen-marketplace(install versions 0.7.0 through 0.7.7 — each tries a differentuserConfigwiring, all broken)claude plugin list --json→ shows config correctly includingmcpServerswith${user_config.*}refsclaude mcp list→claude-image-tts-genis missing (no error)- Swap to v0.7.8 which reverts to
${SHELL_VAR:-default}→ server appears as connected
Expected
Either:
${user_config.<field>}references inmcpServers.envresolve correctly at spawn time (expected behavior per the plugin manifest schema docs)- OR a clear error surfaces in
/plugin→ Errors when resolution fails
Actual
Silent spawn failure. Plugin is enabled, config is loaded, but the MCP server is never registered. /plugin → Errors tab is empty.
What we tried
Across 8 hotfix releases (0.7.0 → 0.7.7):
- Inline
mcpServersinplugin.jsonwith${user_config.*}refs → "Missing required user configuration value" at enable time - Same, with
${user_config.*:-default}fallbacks → same error - External
.mcp.jsonat plugin root → plugin enables but MCP silently doesn't spawn;claude mcp getshows "Scope: Project config" (the file was being picked up as project-level MCP config where${CLAUDE_PLUGIN_ROOT}doesn't resolve — separate bug worth noting) - Renamed external file to
plugin-mcp.jsonwith explicit"mcpServers": "./plugin-mcp.json"pointer in plugin.json → plugin enables, config loaded perclaude plugin list, MCP silently doesn't spawn - Mark required field
required: truewith a user-provided value → no change - Upgraded from Claude Code 2.1.112 → 2.1.116 → no change
Only fix was reverting mcpServers.env to shell env var interpolation + dropping userConfig entirely.
Reference plugin
https://github.com/sherifButt/claude-image-tts-gen
- v0.7.7 (broken): https://github.com/sherifButt/claude-image-tts-gen/blob/v0.7.7/.claude-plugin/plugin.json
- v0.7.8 (works): https://github.com/sherifButt/claude-image-tts-gen/blob/v0.7.8/.claude-plugin/plugin.json
- Diff: https://github.com/sherifButt/claude-image-tts-gen/compare/v0.7.7...v0.7.8
Impact
Plugin authors who want to use userConfig for install-time prompts + keychain storage of secrets are blocked. Every plugin using ${user_config.*} in mcpServers.env is silently broken with no diagnostic — authors have no way to know their plugin's MCP server isn't spawning unless they manually run claude mcp list.
Related
Possibly related: #51538 — /plugin Configure UI Tab/Enter don't advance between userConfig fields. Both suggest the userConfig feature is fresh and rough.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗