Skip to content

User Config Interpolation #217

Closed

Description

@dandelionrosegroup

Title

${user_config.*} values in manifest env block are not interpolated into spawned process environment

Description

MCPB-Bug-Report-user_config-interpolation.md

When a manifest defines an environment variable using ${user_config.*} interpolation in the mcp_config.env block, the variable is absent from the spawned server process's environment — even though the user has configured the value correctly in Claude Desktop's settings UI and the settings JSON file contains the expected value.

Built-in interpolation tokens like ${HOME} in the same env block resolve correctly, confirming that environment variable passing itself works. Only the ${user_config.*} resolver appears broken.

Reproduction Steps

  1. Create a manifest with a user_config field and reference it in mcp_config.env:
{
  "manifest_version": "0.4",
  "server": {
    "type": "uv",
    "mcp_config": {
      "env": {
        "LOCKSTEP_DATA_DIR": "${user_config.data_directory}"
      },
      "platform_overrides": {
        "darwin": {
          "env": {
            "PATH": "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${HOME}/.local/bin"
          }
        }
      }
    }
  },
  "user_config": {
    "data_directory": {
      "type": "directory",
      "title": "Data Directory",
      "description": "Where Lockstep stores chains, tickets, and capacity data",
      "required": true,
      "default": "${HOME}/.lockstep/data"
    }
  }
}
  1. Install the extension in Claude Desktop (macOS).
  2. Open the extension settings in Claude Desktop UI.
  3. Set data_directory to a custom path (e.g., /Users/me/my-project/data).
  4. Confirm the settings JSON is correct:
~/Library/Application Support/Claude/Claude Extensions Settings/local.mcpb.<extension-id>.json

Contents:

{
  "isEnabled": true,
  "userConfig": {
    "data_directory": "/Users/me/my-project/data"
  }
}
  1. Restart Claude Desktop.
  2. Inspect the spawned process environment:
ps -p $(pgrep -f "server.py") -wwE | tr ' ' '\n' | grep LOCKSTEP

Expected Behavior

LOCKSTEP_DATA_DIR=/Users/me/my-project/data should appear in the process environment.

Actual Behavior

LOCKSTEP_DATA_DIR is completely absent from the process environment. It is not set to empty string — it simply doesn't exist.

Meanwhile, ${HOME} in the same platform_overrides.darwin.env.PATH resolves correctly to the user's home directory.

Impact

This is a data-loss-adjacent bug for any extension that uses user_config to configure file paths. When ${user_config.*} silently fails:

  • The server starts without the expected env var
  • Server-side fallback logic kicks in (if any), typically defaulting to ~/.extension-name/data
  • The user sees an empty/default state instead of their actual data
  • No error is raised anywhere — the failure is completely silent
  • Users believe their data is lost (it's not — it's just in the original location, unreachable by the server)

This was discovered during a v0.1.0 → v0.2.0 upgrade of a sideloaded MCPB extension. The user had configured a custom data directory through the UI. After reinstalling the updated bundle, the server started with no data visible despite the settings JSON containing the correct path.

Environment

  • Claude Desktop: macOS (latest as of 2026-03-21)
  • manifest_version: "0.4"
  • server.type: "uv"
  • Extension source: Sideloaded .mcpb file (not from directory)
  • macOS version: Sequoia (Apple Silicon, M4 Max)

Workaround

Users can bypass the broken interpolation by creating a config.json in the extension's data directory that the server reads directly, or by using a startup script that sets the env var before launching the server. Neither of these should be necessary — the manifest spec documents ${user_config.*} as a supported interpolation pattern.

Additional Context

The MANIFEST.md spec explicitly shows ${user_config.allowed_directories} as an interpolation example in the env block, confirming this is intended functionality. The bug is in the resolver implementation within Claude Desktop's extension loader, not in the spec.

Activity

  1. bryan-anthropic commented on Mar 25, 2026

    @bryan-anthropic
    Collaborator

    Thanks for the detailed report. We've reviewed the mcpb interpolation logic and the code path for ${user_config.*} substitution in env blocks appears correct — variables are collected, merged with defaults, and replaceVariables() recursively processes the entire mcp_config object including the env block.

    We're investigating whether the issue may be in how Claude Desktop reads and passes user configuration values to the extension runtime. The symptom you describe (variable completely absent rather than empty) is consistent with the userConfig object not being passed to the interpolation function at all.

    To help us narrow this down, could you share:

    1. Your manifest's user_config and mcp_config.env sections (redacting any sensitive values)
    2. The relevant section from your Claude Desktop settings JSON showing the configured value

    In the meantime, your workaround of using a separate config file the server reads directly is a reasonable approach.

  2. dandelionrosegroup commented on Mar 25, 2026

    @dandelionrosegroup
    Author

    Hi Bryan,

    Thanks for the quick response, and for confirming that the interpolation logic in the codebase looks correct. Your hypothesis that the userConfig object isn't being passed to the interpolation function at all aligns perfectly with what we observed: the variable is completely absent from the process environment rather than resolving to an empty string.

    Here are the two pieces you requested:

    1. Manifest user_config and mcp_config.env sections:

    "server": {
      "type": "uv",
      "entry_point": "src/server.py",
      "mcp_config": {
        "command": "uv",
        "args": [
          "run", "--python", "3.11",
          "--with", "mcp>=1.0.0",
          "--with", "pydantic>=2.0.0",
          "--with", "PyYAML>=6.0",
          "src/server.py"
        ],
        "env": {
          "LOCKSTEP_DATA_DIR": "${user_config.data_directory}"
        },
        "platform_overrides": {
          "darwin": {
            "env": {
              "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:${HOME}/.local/bin"
            }
          },
          "linux": {
            "env": {
              "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOME}/.local/bin"
            }
          }
        }
      }
    }
    "user_config": {
      "data_directory": {
        "type": "directory",
        "title": "Data Directory",
        "description": "Where Lockstep stores chains, tickets, and capacity data",
        "required": true,
        "default": "${HOME}/.lockstep/data"
      }
    }

    2. Claude Desktop settings JSON:

    File location: ~/Library/Application Support/Claude/Claude Extensions Settings/local.mcpb.jack-daniel-williams-dandelion-rose-group-llc.lockstep-core.json

    {
      "isEnabled": true,
      "userConfig": {
        "data_directory": "/Users/dandelionrosegroup/Projects/drg-plugins/lockstep-protocol-plugin/data"
      }
    }

    Additional context that might help:

    • manifest_version: "0.4"
    • server.type: "uv" (not "node") — possibly relevant if the uv code path handles userConfig differently than the node path
    • Platform: macOS Sequoia, Apple Silicon (M4 Max)
    • The ${HOME} token in platform_overrides.darwin.env.PATH resolves correctly in the same spawned process, which is what pointed us toward the user_config resolver specifically
    • Lockstep Core has been submitted to the Anthropic Directory and is currently in the review pipeline, so this bug will affect directory-installed users as well if approved.

    How we diagnosed this:

    This was discovered during a real-world upgrade test. My wife often tests my builds from a "try to break it" perspective as someone who isn't the creator, upgrading from v0.1.0 to v0.2.0 cold. I uninstalled v0.1.0, installed v0.2.0, and got an empty dashboard with no errors. My AI partner Kai (his naming choice but it's Claude, via a Claude.ai Project) and I then traced through the resolution logic together: Kai inspected the process environment via ps -p <pid> -wwE, confirmed LOCKSTEP_DATA_DIR was completely absent while PATH (with ${HOME}) resolved fine, and identified the user_config resolver as the specific failure point.

    We built and shipped the diagnostic tooling as a patch release (v0.2.1), startup logging that reports the resolved data directory and resolution method to stderr, plus a diagnostics block in our dashboard response so users can immediately see if their configuration is reaching the server. We also wrote an UPGRADE.md documenting the workaround (a config.json file adjacent to the server entry point that bypasses MCPB interpolation entirely).

    Happy to provide any additional information or test builds if that would help. Thanks again for looking into this.

    Jack

  3. added a commit that references this issue on Mar 26, 2026
    cb052a2
  4. bryan-anthropic commented on Mar 26, 2026

    @bryan-anthropic
    Collaborator

    Thanks for the incredibly thorough report and repro details — they helped us find the root cause quickly.

    Turns out this is actually a bug in the mcpb CLI, not Claude Desktop. When platform_overrides.darwin.env is present, it was replacing the entire base mcp_config.env instead of merging with it. So your LOCKSTEP_DATA_DIR was being resolved correctly from ${user_config.data_directory}, then immediately discarded when the darwin PATH override overwrote the whole env object.

    That's why ${HOME} in PATH worked fine (it was in the surviving platform override) while ${user_config.data_directory} appeared broken (it was in the base env that got dropped).

    Fix is up in #221 — one-line change, merges instead of replaces. Once that ships, your manifest should work as-is without the config.json workaround.

  5. dandelionrosegroup commented on Apr 8, 2026

    @dandelionrosegroup
    Author

    '''
    Hey Bryan,

    Sorry to bother you as you're probably busy, but really quickly, how do I know if this fix has made it into my version of Claude Desktop so I can make the appropriate changes to my code?

    Really appreciate you, sir!
    Jack
    '''

  6. bryan-anthropic commented on Apr 13, 2026

    @bryan-anthropic
    Collaborator

    Hi Jack — good question. The fix (PR #221) hasn't merged yet, so it's not in any released version of the mcpb CLI or Claude Desktop. Once #221 merges and a new CLI version is published, Claude Desktop will pick it up in a subsequent update. There isn't currently a way to check the bundled CLI version from within Claude Desktop, but we'll update this thread when the fix ships.

    In the meantime, your config.json workaround is the right approach.

  7. added a commit that references this issue on Apr 13, 2026
    7f4596d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions