1111import yaml
1212from pydantic import ValidationError
1313
14- from .models import EnvironmentConfig
1514from .models import HoloVizMCPConfig
1615
1716logger = logging .getLogger (__name__ )
@@ -24,14 +23,16 @@ class ConfigurationError(Exception):
2423class ConfigLoader :
2524 """Loads and manages HoloViz MCP configuration."""
2625
27- def __init__ (self , env_config : Optional [EnvironmentConfig ] = None ):
26+ def __init__ (self , config : Optional [HoloVizMCPConfig ] = None ):
2827 """Initialize configuration loader.
2928
3029 Args:
31- env_config: Environment configuration. If None, loads from environment.
30+ config: Pre-configured HoloVizMCPConfig with environment paths.
31+ If None, loads paths from environment. Configuration will
32+ still be loaded from files even if this is provided.
3233 """
33- self .env_config = env_config or EnvironmentConfig . from_environment ()
34- self ._config : Optional [HoloVizMCPConfig ] = None
34+ self ._env_config = config
35+ self ._loaded_config : Optional [HoloVizMCPConfig ] = None
3536
3637 def load_config (self ) -> HoloVizMCPConfig :
3738 """Load configuration from files and environment.
@@ -44,14 +45,20 @@ def load_config(self) -> HoloVizMCPConfig:
4445 ------
4546 ConfigurationError: If configuration cannot be loaded or is invalid.
4647 """
47- if self ._config is not None :
48- return self ._config
48+ if self ._loaded_config is not None :
49+ return self ._loaded_config
4950
50- # Start with default configuration
51+ # Get environment config (from parameter or environment)
52+ if self ._env_config is not None :
53+ env_config = self ._env_config
54+ else :
55+ env_config = HoloVizMCPConfig .from_environment ()
56+
57+ # Start with default configuration dict
5158 config_dict = self ._get_default_config ()
5259
5360 # Load from default config file if it exists
54- default_config_file = self . env_config .default_dir / "config.yaml"
61+ default_config_file = env_config .default_dir / "config.yaml"
5562 if default_config_file .exists ():
5663 try :
5764 default_config = self ._load_yaml_file (default_config_file )
@@ -61,22 +68,42 @@ def load_config(self) -> HoloVizMCPConfig:
6168 logger .warning (f"Failed to load default config from { default_config_file } : { e } " )
6269
6370 # Load from user config file if it exists
64- user_config_file = self . env_config .config_file_path ()
71+ user_config_file = env_config .config_file_path ()
6572 if user_config_file .exists ():
6673 user_config = self ._load_yaml_file (user_config_file )
74+ # Filter out any unknown fields to prevent validation errors
75+ user_config = self ._filter_known_fields (user_config )
6776 config_dict = self ._merge_configs (config_dict , user_config )
6877 logger .info (f"Loaded user configuration from { user_config_file } " )
6978
7079 # Apply environment variable overrides
7180 config_dict = self ._apply_env_overrides (config_dict )
7281
73- # Validate and create configuration
82+ # Add the environment paths to the config dict
83+ config_dict .update (
84+ {
85+ "user_dir" : env_config .user_dir ,
86+ "default_dir" : env_config .default_dir ,
87+ "repos_dir" : env_config .repos_dir ,
88+ }
89+ )
90+
91+ # Create the final configuration
7492 try :
75- self ._config = HoloVizMCPConfig (** config_dict )
93+ self ._loaded_config = HoloVizMCPConfig (** config_dict )
7694 except ValidationError as e :
7795 raise ConfigurationError (f"Invalid configuration: { e } " ) from e
7896
79- return self ._config
97+ return self ._loaded_config
98+
99+ def _filter_known_fields (self , config_dict : dict [str , Any ]) -> dict [str , Any ]:
100+ """Filter out unknown fields that aren't part of the HoloVizMCPConfig schema.
101+
102+ This prevents validation errors when loading user config files that might
103+ contain extra fields.
104+ """
105+ known_fields = {"server" , "docs" , "resources" , "prompts" , "user_dir" , "default_dir" , "repos_dir" }
106+ return {k : v for k , v in config_dict .items () if k in known_fields }
80107
81108 def _get_default_config (self ) -> dict [str , Any ]:
82109 """Get default configuration dictionary."""
@@ -190,23 +217,28 @@ def _apply_env_overrides(self, config: dict[str, Any]) -> dict[str, Any]:
190217
191218 def get_repos_dir (self ) -> Path :
192219 """Get the repository download directory."""
193- return self .env_config .repos_dir
220+ config = self .load_config ()
221+ return config .repos_dir
194222
195223 def get_resources_dir (self ) -> Path :
196224 """Get the resources directory."""
197- return self .env_config .resources_dir ()
225+ config = self .load_config ()
226+ return config .resources_dir ()
198227
199228 def get_prompts_dir (self ) -> Path :
200229 """Get the prompts directory."""
201- return self .env_config .prompts_dir ()
230+ config = self .load_config ()
231+ return config .prompts_dir ()
202232
203233 def get_best_practices_dir (self ) -> Path :
204234 """Get the best practices directory."""
205- return self .env_config .best_practices_dir ()
235+ config = self .load_config ()
236+ return config .best_practices_dir ()
206237
207238 def create_default_user_config (self ) -> None :
208239 """Create a default user configuration file."""
209- config_file = self .env_config .config_file_path ()
240+ config = self .load_config ()
241+ config_file = config .config_file_path ()
210242
211243 # Create directories if they don't exist
212244 config_file .parent .mkdir (parents = True , exist_ok = True )
@@ -246,9 +278,13 @@ def reload_config(self) -> HoloVizMCPConfig:
246278 -------
247279 Reloaded configuration.
248280 """
249- self ._config = None
281+ self ._loaded_config = None
250282 return self .load_config ()
251283
284+ def clear_cache (self ) -> None :
285+ """Clear the cached configuration to force reload on next access."""
286+ self ._loaded_config = None
287+
252288
253289# Global configuration loader instance
254290_config_loader : Optional [ConfigLoader ] = None
0 commit comments