-
Notifications
You must be signed in to change notification settings - Fork 495
update settings fetch to prioritize config_path
config AND secrets
#86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… the same dir before config discovery method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jtanningbed thank you for the catch! really great find. I have one suggestion for how to improve the function further. LMK what you think and I can merge in the change after that!
@@ -330,31 +330,57 @@ def deep_merge(base: dict, update: dict) -> dict: | |||
if _settings: | |||
return _settings | |||
|
|||
config_file = Path(config_path) if config_path else Settings.find_config() | |||
import yaml # pylint: disable=C0415 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue with the new approach is that in the case we find the config using Settings.find_config
, we don't look for the secrets in the same directory as that, so we have the same issue again.
What I'd recommend is checking for the secrets first in the same directory where we find the config file in, before resorting to walking up the tree.
How about something like this?
def get_settings(config_path: str | None = None) -> Settings:
"""Get settings, loading config and secrets from an explicit path or discovered locations.
First, load the config file and check its directory for a secrets file using
both naming conventions. If no secrets file is found there, fall back to using the
discovery method to locate a secrets file.
"""
def deep_merge(base: dict, update: dict) -> dict:
"""Recursively merge two dictionaries."""
merged = base.copy()
for key, value in update.items():
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
merged[key] = deep_merge(merged[key], value)
else:
merged[key] = value
return merged
global _settings
if _settings:
return _settings
import yaml # pylint: disable=C0415
merged_settings = {}
# Determine the config file to use.
if config_path:
config_file = Path(config_path)
else:
config_file = Settings.find_config()
if config_file and config_file.exists():
# Load the main config file.
with open(config_file, "r", encoding="utf-8") as f:
merged_settings = yaml.safe_load(f) or {}
# Try to load secrets from the same directory as the config file.
config_dir = config_file.parent
secrets_found = False
for secrets_filename in ["mcp-agent.secrets.yaml", "mcp_agent.secrets.yaml"]:
secrets_file = config_dir / secrets_filename
if secrets_file.exists():
with open(secrets_file, "r", encoding="utf-8") as f:
secrets = yaml.safe_load(f) or {}
merged_settings = deep_merge(merged_settings, secrets)
secrets_found = True
# Fallback: If no secrets were found in the config's directory, use the discovery method.
if not secrets_found:
discovered_secrets = Settings.find_secrets()
if discovered_secrets and discovered_secrets.exists():
with open(discovered_secrets, "r", encoding="utf-8") as f:
secrets = yaml.safe_load(f) or {}
merged_settings = deep_merge(merged_settings, secrets)
_settings = Settings(**merged_settings)
return _settings
# No valid config file was found; return default settings.
_settings = Settings()
return _settings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah right, sounds good to me. i'll make that update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you @jtanningbed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the update to the improved control flow, just wanted to mention another change I added and double check this makes sense:
- if config_path is specified (ie. if someone init MCPApp(settings='/path/to/config.yaml')), if the path doesn't resolve to a config file, we should NOT perform discovery. Instead, we fail immediately (this is to prevent obfuscating issues w/ configurations for users who may have many config files at different layers).
Basically, if someone tries to explicitly specify a path that doesn't resolve and we were to find_config() and successfully discover a config file via this strategy, there is a chance the config is not the one the user wants and their app runs successfully. but if they update their target config file and the app doesn't appear to use the new changes it could be a nightmare to try and debug why everything 'seems to work' but 'for some reason' their config changes aren't applying.
With the additional change - if the path doesn't resolve to a real file, we fail fast like so:
Config file not found: /Users/me/testpath.yaml
Error: Config file not found: /Users/me/testpath.yaml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and -- if config_path not specified, we still do discovery and allow init for the default Settings() if no config file found. i'm unaware of whether there are working scenarios when there is no valid config yaml file found, so I left that part alone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is perfectly reasonable, thank you!
…ound through discovery; faster failure if config_path specified but not resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jtanningbed, change is looking good. Just 2 more comments. Approving since we're almost there, but would love to get those updated before we merge in.
done thanks! |
Fix for loading secrets alongside explicit config files
Problem
When an explicit config path is provided to
get_settings()
, the function looks for secrets usingSettings.find_secrets()
, which starts from the current working directory rather than the directory containing the config file. This causes issues when running tools from a different directory from where the config files are located.Solution
This PR modifies
get_settings()
to prioritize looking for secrets in the same directory as an explicitly provided config file:mcp-agent.secrets.yaml
andmcp_agent.secrets.yaml
)Benefits
Testing
Tested with a CLI tool that uses MCP agent and has its config/secrets stored in a subdirectory. Before this change, the tool needed to be run from the directory containing the config/secrets. After this change, it can be run from any directory.
Worth mentioning that a previous version had this behavior, but I don't have any context on whether this was a deliberate change: