Hmbown/CodeWhale · error

invalid environment placeholder in MCP config value

Error message

invalid environment placeholder in MCP config value

What it means

The placeholder parser found ${...} but the inner name fails validation: empty (${}) or containing characters outside ASCII letters, digits, and underscore. Names must be plain identifiers so environment lookup is unambiguous; substitution syntaxes from other tools are not supported.

Source

Thrown at crates/tui/src/mcp.rs:81

///
/// On a missing or malformed placeholder the error names only the offending
/// variable, never the surrounding value, so a secret-bearing string is never
/// echoed into logs or error output.
fn expand_env_placeholders_with(
    value: &str,
    environment: Option<&crate::plugins::HostEnvironment>,
) -> Result<String> {
    let mut out = String::new();
    let mut rest = value;
    while let Some(start) = rest.find("${") {
        out.push_str(&rest[..start]);
        let after = &rest[start + 2..];
        let Some(end) = after.find('}') else {
            anyhow::bail!("unterminated environment placeholder in MCP config value");
        };
        let name = &after[..end];
        if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
            anyhow::bail!("invalid environment placeholder in MCP config value");
        }
        let env_value = environment
            .map_or_else(|| std::env::var(name), |env| env.var(name))
            .with_context(|| {
                format!("environment variable {name} required by MCP config is not set")
            })?;
        out.push_str(&env_value);
        rest = &after[end + 1..];
    }
    out.push_str(rest);
    Ok(out)
}

#[cfg(test)]
fn expand_env_placeholders(value: &str) -> Result<String> {
    expand_env_placeholders_with(value, None)
}

View on GitHub (pinned to 8880682c63)

Solutions

  1. Use plain identifier names: letters, digits, underscore only, e.g. ${MY_VAR}
  2. Express defaults by exporting the variable in the launching environment instead of inline default syntax
  3. If you control the variable, rename it to an identifier-safe name

Example fix

# before
"url": "https://${host-name}/mcp"
# after: identifier-safe name, exported in the environment
export MCP_HOST_NAME=api.example.com
"url": "https://${MCP_HOST_NAME}/mcp"
Defensive patterns

Strategy: validation

Validate before calling

// Validate placeholder names at config load time
fn placeholder_names_ok(v: &str) -> bool {
    let mut rest = v;
    while let Some(start) = rest.find("${") {
        let after = &rest[start + 2..];
        let Some(end) = after.find('}') else { return false };
        let name = &after[..end];
        if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
            return false;
        }
        rest = &after[end + 1..];
    }
    true
}

Prevention

When it happens

Trigger: Values like ${}, ${MY-VAR}, ${VAR:-default}, ${var/cmd}, or placeholders containing whitespace - any inner text that is not a bare ASCII identifier.

Common situations: Copying docker-compose or bash substitution syntax (${VAR:-default}) into MCP config, hyphenated variable names, placeholders mangled by copy-paste from documentation.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/1ada6116dc085e0d. Report an issue: GitHub.