Plugin MCP server silently fails to spawn when env block references ${user_config.*}

Resolved 💬 1 comment Opened Apr 21, 2026 by sherifButt Closed Apr 22, 2026

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

  1. /plugin marketplace add sherifButt/claude-image-tts-gen
  2. /plugin install claude-image-tts-gen@claude-image-tts-gen-marketplace (install versions 0.7.0 through 0.7.7 — each tries a different userConfig wiring, all broken)
  3. claude plugin list --json → shows config correctly including mcpServers with ${user_config.*} refs
  4. claude mcp listclaude-image-tts-gen is missing (no error)
  5. Swap to v0.7.8 which reverts to ${SHELL_VAR:-default} → server appears as connected

Expected

Either:

  • ${user_config.<field>} references in mcpServers.env resolve 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 mcpServers in plugin.json with ${user_config.*} refs → "Missing required user configuration value" at enable time
  • Same, with ${user_config.*:-default} fallbacks → same error
  • External .mcp.json at plugin root → plugin enables but MCP silently doesn't spawn; claude mcp get shows "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.json with explicit "mcpServers": "./plugin-mcp.json" pointer in plugin.json → plugin enables, config loaded per claude plugin list, MCP silently doesn't spawn
  • Mark required field required: true with 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

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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗