bytedance/deer-flow · error · HTTPException
Cannot set env key '{k}' to masked value '***'; provide a re
Error message
Cannot set env key '{k}' to masked value '***'; provide a real value. What it means
400 raised when an MCP config update sends the masked sentinel '***' for an env key that does not exist in the currently stored server config. Masked GET responses are meant to round-trip: existing keys sent back as '***' keep their stored value. A masked value for a brand-new key has nothing to preserve, so it is rejected.
Source
Thrown at backend/app/gateway/routers/mcp.py:680
GET (masked) → modify enabled → PUT (masked values sent back).
This function ensures masked values (``***``) are replaced with the
real secrets from the current on-disk config.
``***`` is only accepted for keys that already exist in *existing*.
New keys must provide a real value.
For OAuth secrets, ``None`` means "preserve the existing stored value"
so masked GET responses can be safely round-tripped. To explicitly clear
a stored secret, clients may send an empty string, which is converted
to ``None`` before persisting.
"""
merged_env = {}
for k, v in incoming.env.items():
if v == _MASKED_VALUE:
if k in existing.env:
merged_env[k] = existing.env[k]
else:
raise HTTPException(
status_code=400,
detail=f"Cannot set env key '{k}' to masked value '***'; provide a real value.",
)
else:
merged_env[k] = v
merged_headers = {}
for k, v in incoming.headers.items():
if v == _MASKED_VALUE:
if k in existing.headers:
merged_headers[k] = existing.headers[k]
else:
raise HTTPException(
status_code=400,
detail=f"Cannot set header '{k}' to masked value '***'; provide a real value.",
)
else:
merged_headers[k] = vView on GitHub (pinned to 1dd6ba1acb)
Solutions
- Provide the real secret value for any env key not already stored
- Only send '***' for keys that exist in the stored config (those are preserved on merge)
- Maintain the authoritative unmasked config outside the API and diff against it, not against masked GET output
Example fix
# before
env: {"EXISTING_KEY": "***", "NEW_KEY": "***"} # NEW_KEY -> 400
# after
env: {"EXISTING_KEY": "***", "NEW_KEY": "real-secret-value"} Defensive patterns
Strategy: validation
Validate before calling
function resolveMaskedEnv(incoming: Record<string, string>, existing: Record<string, string> | undefined): Record<string, string> { const out: Record<string, string> = {}; for (const [k, v] of Object.entries(incoming)) { if (v === '***') { if (!existing || !(k in existing)) throw new Error(`new env key '${k}' needs a real value, not the mask`); out[k] = existing[k]; } else out[k] = v; } return out; } Type guard
function isMasked(v: unknown): v is '***' { return v === '***'; } Try / catch
null
Prevention
- Fetch the unmasked truth from your secret store, use GET output only as structure reference
- Send '***' only for keys you verified exist server-side
- When adding env keys in a UI, blank the masked placeholder instead of submitting it
When it happens
Trigger: PUT to /api/mcp/config adding env: {"API_KEY": "***"} when the stored server has no API_KEY; re-submitting a masked GET body but renaming or adding env keys; creating a new server by templating another server's masked response.
Common situations: Config round-trip edit flows where the user adds a new secret field but leaves the masked placeholder; automation that merges masked snapshots into new server definitions.
Related errors
- Cannot set extra config key '{key}' to masked value '***'; p
- Cannot set header '{k}' to masked value '***'; provide a rea
- MCP server '{server_name}' with stdio transport requires a c
- MCP server '{server_name}' command must be a single executab
- MCP server '{name}' uses disallowed stdio command '{command_
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/9b3036a8e33a7204.
Report an issue: GitHub.