{"record":{"id":"c90529c437429697","repo":"bytedance/deer-flow","slug":"cannot-set-extra-config-key-key-to-masked-valu","errorCode":null,"errorMessage":"Cannot set extra config key '{key}' to masked value '***'; provide a real value.","messagePattern":"Cannot set extra config key '(.+?)' to masked value '\\*\\*\\*'; provide a real value\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/app/gateway/routers/mcp.py","lineNumber":461,"sourceCode":"\n\ndef _is_sensitive_extra_key(key: str) -> bool:\n    return bool(_SENSITIVE_EXTRA_KEY_RE.search(_normalize_config_key(key)))\n\n\ndef _mask_sensitive_extra_value(value: Any) -> Any:\n    if isinstance(value, dict):\n        return {key: _MASKED_VALUE if _is_sensitive_extra_key(str(key)) else _mask_sensitive_extra_value(nested) for key, nested in value.items()}\n    if isinstance(value, list):\n        return [_mask_sensitive_extra_value(item) for item in value]\n    return value\n\n\ndef _merge_extra_value_preserving_masked(key: str, incoming_value: Any, existing_value: Any, *, existing_present: bool) -> Any:\n    if incoming_value == _MASKED_VALUE and _is_sensitive_extra_key(key):\n        if existing_present:\n            return existing_value\n        raise HTTPException(\n            status_code=400,\n            detail=f\"Cannot set extra config key '{key}' to masked value '***'; provide a real value.\",\n        )\n\n    if isinstance(incoming_value, dict) and isinstance(existing_value, dict):\n        merged: dict[str, Any] = {}\n        for nested_key, nested_value in incoming_value.items():\n            nested_present = nested_key in existing_value\n            merged[nested_key] = _merge_extra_value_preserving_masked(\n                str(nested_key),\n                nested_value,\n                existing_value.get(nested_key),\n                existing_present=nested_present,\n            )\n        return merged\n\n    if isinstance(incoming_value, list) and isinstance(existing_value, list) and len(incoming_value) == len(existing_value):\n        return [_merge_extra_value_preserving_masked(key, nested_value, existing_value[index], existing_present=True) for index, nested_value in enumerate(incoming_value)]","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/mcp.py#L443-L479","documentation":"400 raised when an MCP configuration update sends the literal masked value '***' for a sensitive extra-config key that does not already exist in the stored configuration. GET responses mask sensitive values; when updating, the server preserves existing values for masked round-trips, but a masked value with no stored original cannot be resolved, so it is rejected.","triggerScenarios":"PUT to the MCP config endpoint where extra config contains {'api_token': '***'} for a key that was never set before; constructing a config payload by copying a masked GET response for a brand-new server entry; adding a new sensitive nested key whose value came from another server's masked response.","commonSituations":"Config-as-code workflows that fetch the live (masked) config and re-submit it as a template for new servers; UI edits that pre-fill masked placeholders into create forms.","solutions":["Send a real secret value instead of '***' for any sensitive key that is newly added","Keep an unmasked source of truth (secret manager or local file) and merge masked GET output with real values before submitting","When round-tripping an existing server's config, leave sensitive keys with '***' only if they already exist in stored config (then they are preserved)"],"exampleFix":"# before\nextra: {\"api_token\": \"***\"}   # new key, masked value -> 400\n# after\nextra: {\"api_token\": \"sk-real-secret-from-vault\"}","handlingStrategy":"validation","validationCode":"function assertNoMaskedNewKeys(incomingExtra: Record<string, unknown>, existingExtra: Record<string, unknown> | undefined) { for (const [k, v] of Object.entries(incomingExtra)) { if (v === '***' && !(existingExtra && k in existingExtra)) throw new Error(`provide a real value for new key ${k}`); if (typeof v === 'object' && v !== null) assertNoMaskedNewKeys(v as any, existingExtra?.[k] as any); } }","typeGuard":"function isMaskedSentinel(v: unknown): v is '***' { return v === '***'; }","tryCatchPattern":"try { await putMcpConfig(payload); } catch (e) { if (e.status === 400 && /masked value/.test(e.detail)) { /* replace *** for the named key with the real secret and retry once */ } throw e; }","preventionTips":["Never build payloads by copying masked GET responses for new entries","Inject real secrets from a vault at submit time","Round-trip masked values only for keys already present in stored config"],"tags":["mcp","config","secrets","masking","http-400"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}