Repository navigation
Description
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
- Create a manifest with a
user_configfield and reference it inmcp_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"
}
}
}- Install the extension in Claude Desktop (macOS).
- Open the extension settings in Claude Desktop UI.
- Set
data_directoryto a custom path (e.g.,/Users/me/my-project/data). - 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"
}
}- Restart Claude Desktop.
- Inspect the spawned process environment:
ps -p $(pgrep -f "server.py") -wwE | tr ' ' '\n' | grep LOCKSTEPExpected 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
.mcpbfile (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
bryan-anthropic commentedon Mar 25, 2026
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, andreplaceVariables()recursively processes the entiremcp_configobject including theenvblock.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
userConfigobject not being passed to the interpolation function at all.To help us narrow this down, could you share:
user_configandmcp_config.envsections (redacting any sensitive values)In the meantime, your workaround of using a separate config file the server reads directly is a reasonable approach.
dandelionrosegroup commentedon Mar 25, 2026
Hi Bryan,
Thanks for the quick response, and for confirming that the interpolation logic in the codebase looks correct. Your hypothesis that the
userConfigobject 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_configandmcp_config.envsections: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 handlesuserConfigdifferently than the node path${HOME}token inplatform_overrides.darwin.env.PATHresolves correctly in the same spawned process, which is what pointed us toward theuser_configresolver specificallyHow 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, confirmedLOCKSTEP_DATA_DIRwas completely absent whilePATH(with${HOME}) resolved fine, and identified theuser_configresolver 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
diagnosticsblock 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 (aconfig.jsonfile 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
fix: merge platform_overrides env with base env instead of replacing
bryan-anthropic commentedon Mar 26, 2026
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.envis present, it was replacing the entire basemcp_config.envinstead of merging with it. So yourLOCKSTEP_DATA_DIRwas being resolved correctly from${user_config.data_directory}, then immediately discarded when the darwinPATHoverride overwrote the whole env object.That's why
${HOME}inPATHworked 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.
dandelionrosegroup commentedon Apr 8, 2026
'''
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
'''
bryan-anthropic commentedon Apr 13, 2026
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.
Merge pull request #221 from modelcontextprotocol/fix/platform-overri…