mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 08:25:57 +00:00
Fix env resolution in MCP config lists (#2556)
* Fix env resolution in MCP config lists * fix:unset env variable and consistent function --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
@@ -141,7 +141,7 @@ class ExtensionsConfig(BaseModel):
|
|||||||
try:
|
try:
|
||||||
with open(resolved_path, encoding="utf-8") as f:
|
with open(resolved_path, encoding="utf-8") as f:
|
||||||
config_data = json.load(f)
|
config_data = json.load(f)
|
||||||
cls.resolve_env_variables(config_data)
|
config_data = cls.resolve_env_variables(config_data)
|
||||||
return cls.model_validate(config_data)
|
return cls.model_validate(config_data)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
raise ValueError(f"Extensions config file at {resolved_path} is not valid JSON: {e}") from e
|
raise ValueError(f"Extensions config file at {resolved_path} is not valid JSON: {e}") from e
|
||||||
@@ -149,7 +149,7 @@ class ExtensionsConfig(BaseModel):
|
|||||||
raise RuntimeError(f"Failed to load extensions config from {resolved_path}: {e}") from e
|
raise RuntimeError(f"Failed to load extensions config from {resolved_path}: {e}") from e
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_env_variables(cls, config: dict[str, Any]) -> dict[str, Any]:
|
def resolve_env_variables(cls, config: Any) -> Any:
|
||||||
"""Recursively resolve environment variables in the config.
|
"""Recursively resolve environment variables in the config.
|
||||||
|
|
||||||
Environment variables are resolved using the `os.getenv` function. Example: $OPENAI_API_KEY
|
Environment variables are resolved using the `os.getenv` function. Example: $OPENAI_API_KEY
|
||||||
@@ -160,23 +160,26 @@ class ExtensionsConfig(BaseModel):
|
|||||||
Returns:
|
Returns:
|
||||||
The config with environment variables resolved.
|
The config with environment variables resolved.
|
||||||
"""
|
"""
|
||||||
for key, value in config.items():
|
if isinstance(config, str):
|
||||||
if isinstance(value, str):
|
if not config.startswith("$"):
|
||||||
if value.startswith("$"):
|
return config
|
||||||
env_value = os.getenv(value[1:])
|
env_value = os.getenv(config[1:])
|
||||||
if env_value is None:
|
if env_value is None:
|
||||||
# Unresolved placeholder — store empty string so downstream
|
# Unresolved placeholder — store empty string so downstream
|
||||||
# consumers (e.g. MCP servers) don't receive the literal "$VAR"
|
# consumers (e.g. MCP servers) don't receive the literal "$VAR"
|
||||||
# token as an actual environment value.
|
# token as an actual environment value.
|
||||||
config[key] = ""
|
return ""
|
||||||
else:
|
return env_value
|
||||||
config[key] = env_value
|
|
||||||
else:
|
if isinstance(config, dict):
|
||||||
config[key] = value
|
return {key: cls.resolve_env_variables(value) for key, value in config.items()}
|
||||||
elif isinstance(value, dict):
|
|
||||||
config[key] = cls.resolve_env_variables(value)
|
if isinstance(config, list):
|
||||||
elif isinstance(value, list):
|
return [cls.resolve_env_variables(item) for item in config]
|
||||||
config[key] = [cls.resolve_env_variables(item) if isinstance(item, dict) else item for item in value]
|
|
||||||
|
if isinstance(config, tuple):
|
||||||
|
return tuple(cls.resolve_env_variables(item) for item in config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_enabled_mcp_servers(self) -> dict[str, McpServerConfig]:
|
def get_enabled_mcp_servers(self) -> dict[str, McpServerConfig]:
|
||||||
|
|||||||
@@ -24,6 +24,26 @@ def test_build_server_params_stdio_success():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_extensions_config_resolves_env_variables_inside_nested_collections(monkeypatch):
|
||||||
|
monkeypatch.setenv("MCP_TOKEN", "secret")
|
||||||
|
monkeypatch.delenv("MISSING_TOKEN", raising=False)
|
||||||
|
raw_config = {
|
||||||
|
"args": ["--token", "$MCP_TOKEN", {"nested": ["$MCP_TOKEN", "$MISSING_TOKEN"]}],
|
||||||
|
"tuple_args": ("$MCP_TOKEN", "$MISSING_TOKEN"),
|
||||||
|
"env": {"API_KEY": "$MCP_TOKEN"},
|
||||||
|
"enabled": True,
|
||||||
|
"timeout": 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved = ExtensionsConfig.resolve_env_variables(raw_config)
|
||||||
|
|
||||||
|
assert resolved["args"] == ["--token", "secret", {"nested": ["secret", ""]}]
|
||||||
|
assert resolved["tuple_args"] == ("secret", "")
|
||||||
|
assert resolved["env"] == {"API_KEY": "secret"}
|
||||||
|
assert resolved["enabled"] is True
|
||||||
|
assert resolved["timeout"] == 30
|
||||||
|
|
||||||
|
|
||||||
def test_build_server_params_stdio_requires_command():
|
def test_build_server_params_stdio_requires_command():
|
||||||
config = McpServerConfig(type="stdio", command=None)
|
config = McpServerConfig(type="stdio", command=None)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user