55import os
66import json
77import logging
8- from typing import Dict , Optional , Any , List
8+ import platform
9+ from typing import Dict , Optional , Any , List , Union
910
1011from mcpm .utils .server_config import ServerConfig
1112
@@ -19,24 +20,33 @@ class BaseClientManager:
1920 display_name = "" # Human-readable name (e.g., "Claude Desktop")
2021 download_url = "" # URL to download the client
2122
22- def __init__ (self , config_path : str ):
23- """Initialize with a configuration path"""
24- self .config_path = config_path
23+ def __init__ (self ):
24+ """Initialize the client manager
25+ """
26+ self .config_path = None # To be set by subclasses
2527 self ._config = None
28+ self ._system = platform .system ()
2629
2730 def _load_config (self ) -> Dict [str , Any ]:
2831 """Load client configuration file
2932
3033 Returns:
31- Dict containing the client configuration
34+ Dict containing the client configuration with at least {"mcpServers": {}}
3235 """
36+ # Create empty config with the correct structure
37+ empty_config = {"mcpServers" : {}}
38+
3339 if not os .path .exists (self .config_path ):
3440 logger .warning (f"Client config file not found at: { self .config_path } " )
35- return self . _get_empty_config ()
41+ return empty_config
3642
3743 try :
3844 with open (self .config_path , 'r' ) as f :
39- return json .load (f )
45+ config = json .load (f )
46+ # Ensure mcpServers section exists
47+ if "mcpServers" not in config :
48+ config ["mcpServers" ] = {}
49+ return config
4050 except json .JSONDecodeError :
4151 logger .error (f"Error parsing client config file: { self .config_path } " )
4252
@@ -50,7 +60,7 @@ def _load_config(self) -> Dict[str, Any]:
5060 logger .error (f"Failed to backup corrupt file: { str (e )} " )
5161
5262 # Return empty config
53- return self . _get_empty_config ()
63+ return empty_config
5464
5565 def _save_config (self , config : Dict [str , Any ]) -> bool :
5666 """Save configuration to client config file
@@ -72,14 +82,7 @@ def _save_config(self, config: Dict[str, Any]) -> bool:
7282 logger .error (f"Error saving client config: { str (e )} " )
7383 return False
7484
75- def _get_empty_config (self ) -> Dict [str , Any ]:
76- """Get an empty config structure for this client
77-
78- Returns:
79- Dict containing empty configuration structure
80- """
81- # To be overridden by subclasses
82- return {"mcpServers" : {}}
85+
8386
8487 def get_servers (self ) -> Dict [str , Any ]:
8588 """Get all MCP servers configured for this client
@@ -109,48 +112,39 @@ def get_server(self, server_name: str) -> Optional[ServerConfig]:
109112
110113 # Get the server config and convert to ServerConfig
111114 client_config = servers [server_name ]
112- return self ._convert_from_client_format (server_name , client_config )
115+ return self .from_client_format (server_name , client_config )
113116
114- def _add_server_config (self , server_name : str , server_config : Dict [str , Any ]) -> bool :
115- """Add or update an MCP server in client config using raw config dictionary
117+ def add_server (self , server_config : Union [ ServerConfig , Dict [str , Any ]], name : Optional [ str ] = None ) -> bool :
118+ """Add or update a server in the client config
116119
117- Note: This is an internal method that should generally not be called directly .
118- Use add_server with a ServerConfig object instead for better type safety and validation .
120+ Can accept either a ServerConfig object or a raw dictionary in client format .
121+ When using a dictionary, a name must be provided .
119122
120123 Args:
121- server_name: Name of the server
122- server_config: Server configuration dictionary
124+ server_config: ServerConfig object or dictionary in client format
125+ name: Required server name when using dictionary format
123126
124127 Returns:
125128 bool: Success or failure
126129 """
130+ # Handle direct dictionary input
131+ if isinstance (server_config , dict ):
132+ if name is None :
133+ raise ValueError ("Name must be provided when using dictionary format" )
134+ server_name = name
135+ client_config = server_config # Already in client format
136+ # Handle ServerConfig objects
137+ else :
138+ server_name = server_config .name
139+ client_config = self .to_client_format (server_config )
140+
141+ # Update config directly
127142 config = self ._load_config ()
128-
129- # Initialize mcpServers if it doesn't exist
130- if "mcpServers" not in config :
131- config ["mcpServers" ] = {}
132-
133- # Add or update the server
134- config ["mcpServers" ][server_name ] = server_config
143+ config ["mcpServers" ][server_name ] = client_config
135144
136145 return self ._save_config (config )
137-
138- def add_server (self , server_config : ServerConfig ) -> bool :
139- """Add or update a server using a ServerConfig object
140-
141- This is the preferred method for adding servers as it ensures proper type safety
142- and validation through the ServerConfig object.
143-
144- Args:
145- server_config: StandardServer configuration object
146-
147- Returns:
148- bool: Success or failure
149- """
150- # Default implementation - can be overridden by subclasses
151- return self ._add_server_config (server_config .name , self ._convert_to_client_format (server_config ))
152146
153- def _convert_to_client_format (self , server_config : ServerConfig ) -> Dict [str , Any ]:
147+ def to_client_format (self , server_config : ServerConfig ) -> Dict [str , Any ]:
154148 """Convert ServerConfig to client-specific format with common core fields
155149
156150 This base implementation provides the common core fields (command, args, env)
@@ -202,68 +196,19 @@ def from_client_format(cls, server_name: str, client_config: Dict[str, Any]) ->
202196
203197 return ServerConfig .from_dict (server_data )
204198
205- def _convert_from_client_format (self , server_name : str , client_config : Any ) -> ServerConfig :
206- """Convert client-specific format to ServerConfig
207-
208- This default implementation handles the case where client_config might be a ServerConfig already
209- or needs to be converted using from_client_format.
210-
211- Args:
212- server_name: Name of the server
213- client_config: Client-specific configuration or ServerConfig object
214-
215- Returns:
216- ServerConfig object
217- """
218- # If client_config is already a ServerConfig object, just return it
219- if isinstance (client_config , ServerConfig ):
220- # Ensure the name is set correctly
221- if client_config .name != server_name :
222- client_config .name = server_name
223- return client_config
224-
225- # Otherwise, convert from dict format
226- return self .from_client_format (server_name , client_config )
199+
227200
228201 def list_servers (self ) -> List [str ]:
229202 """List all MCP servers in client config
230203
231204 Returns:
232205 List of server names
233206 """
234- config = self ._load_config ()
235-
236- # Check if mcpServers exists
237- if "mcpServers" not in config :
238- return []
239-
240- return list (config ["mcpServers" ].keys ())
207+ return list (self .get_servers ().keys ())
241208
242- def get_server_configs (self ) -> List [ServerConfig ]:
243- """Get all MCP servers as ServerConfig objects
244-
245- Returns:
246- List of ServerConfig objects
247- """
248- servers = self .get_servers ()
249- return [
250- self ._convert_from_client_format (name , config )
251- for name , config in servers .items ()
252- ]
209+
253210
254- def get_server_config (self , server_name : str ) -> Optional [ServerConfig ]:
255- """Get a specific MCP server as a ServerConfig object
256-
257- Args:
258- server_name: Name of the server
259-
260- Returns:
261- ServerConfig or None if not found
262- """
263- server = self .get_server (server_name )
264- if server :
265- return self ._convert_from_client_format (server_name , server )
266- return None
211+
267212
268213 def remove_server (self , server_name : str ) -> bool :
269214 """Remove an MCP server from client config
@@ -274,19 +219,15 @@ def remove_server(self, server_name: str) -> bool:
274219 Returns:
275220 bool: Success or failure
276221 """
277- config = self ._load_config ()
222+ servers = self .get_servers ()
278223
279- # Check if mcpServers exists
280- if "mcpServers" not in config :
281- logger .warning (f"Cannot remove server { server_name } : mcpServers section doesn't exist" )
282- return False
283-
284224 # Check if the server exists
285- if server_name not in config [ "mcpServers" ] :
225+ if server_name not in servers :
286226 logger .warning (f"Server { server_name } not found in { self .display_name } config" )
287227 return False
288228
289- # Remove the server
229+ # Load full config and remove the server
230+ config = self ._load_config ()
290231 del config ["mcpServers" ][server_name ]
291232
292233 return self ._save_config (config )
@@ -312,39 +253,3 @@ def is_client_installed(self) -> bool:
312253 # Default implementation checks if the config directory exists
313254 # Can be overridden by subclasses
314255 return os .path .isdir (os .path .dirname (self .config_path ))
315-
316- def disable_server (self , server_name : str ) -> bool :
317- """Temporarily disable (stash) a server without removing its configuration
318-
319- Args:
320- server_name: Name of the server to disable
321-
322- Returns:
323- bool: Success or failure
324- """
325- # To be implemented by subclasses
326- raise NotImplementedError ("Subclasses must implement disable_server" )
327-
328- def enable_server (self , server_name : str ) -> bool :
329- """Re-enable (pop) a previously disabled server
330-
331- Args:
332- server_name: Name of the server to enable
333-
334- Returns:
335- bool: Success or failure
336- """
337- # To be implemented by subclasses
338- raise NotImplementedError ("Subclasses must implement enable_server" )
339-
340- def is_server_disabled (self , server_name : str ) -> bool :
341- """Check if a server is currently disabled (stashed)
342-
343- Args:
344- server_name: Name of the server to check
345-
346- Returns:
347- bool: True if server is disabled, False otherwise
348- """
349- # To be implemented by subclasses
350- raise NotImplementedError ("Subclasses must implement is_server_disabled" )
0 commit comments