Skip to content

Commit b5fa302

Browse files
committed
Refactor client manager
1 parent f13feba commit b5fa302

File tree

6 files changed

+205
-606
lines changed

6 files changed

+205
-606
lines changed

src/mcpm/clients/base.py

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,25 @@ def get_servers(self) -> Dict[str, Any]:
9191
config = self._load_config()
9292
return config.get("mcpServers", {})
9393

94-
def get_server(self, server_name: str) -> Optional[Dict[str, Any]]:
95-
"""Get a specific MCP server configuration
94+
def get_server(self, server_name: str) -> Optional[ServerConfig]:
95+
"""Get a server configuration
9696
9797
Args:
98-
server_name: Name of the server to retrieve
98+
server_name: Name of the server
9999
100100
Returns:
101-
Server configuration or None if not found
101+
ServerConfig object if found, None otherwise
102102
"""
103103
servers = self.get_servers()
104-
return servers.get(server_name)
104+
105+
# Check if the server exists
106+
if server_name not in servers:
107+
logger.debug(f"Server {server_name} not found in {self.display_name} config")
108+
return None
109+
110+
# Get the server config and convert to ServerConfig
111+
client_config = servers[server_name]
112+
return self._convert_from_client_format(server_name, client_config)
105113

106114
def _add_server_config(self, server_name: str, server_config: Dict[str, Any]) -> bool:
107115
"""Add or update an MCP server in client config using raw config dictionary
@@ -116,8 +124,16 @@ def _add_server_config(self, server_name: str, server_config: Dict[str, Any]) ->
116124
Returns:
117125
bool: Success or failure
118126
"""
119-
# To be implemented by subclasses
120-
raise NotImplementedError("Subclasses must implement _add_server_config")
127+
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
135+
136+
return self._save_config(config)
121137

122138
def add_server(self, server_config: ServerConfig) -> bool:
123139
"""Add or update a server using a ServerConfig object
@@ -135,19 +151,36 @@ def add_server(self, server_config: ServerConfig) -> bool:
135151
return self._add_server_config(server_config.name, self._convert_to_client_format(server_config))
136152

137153
def _convert_to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]:
138-
"""Convert ServerConfig to client-specific format
154+
"""Convert ServerConfig to client-specific format with common core fields
155+
156+
This base implementation provides the common core fields (command, args, env)
157+
that are used by all client managers. Subclasses can override this method
158+
if they need to add additional client-specific fields.
139159
140160
Args:
141-
server_config: StandardServer configuration
161+
server_config: ServerConfig object
142162
143163
Returns:
144-
Dict containing client-specific configuration
164+
Dict containing client-specific configuration with core fields
145165
"""
146-
# To be implemented by subclasses
147-
raise NotImplementedError("Subclasses must implement _convert_to_client_format")
166+
# Base result containing only essential execution information
167+
result = {
168+
"command": server_config.command,
169+
"args": server_config.args,
170+
}
171+
172+
# Add filtered environment variables if present
173+
non_empty_env = server_config.get_filtered_env_vars()
174+
if non_empty_env:
175+
result["env"] = non_empty_env
176+
177+
return result
148178

149-
def _convert_from_client_format(self, server_name: str, client_config: Dict[str, Any]) -> ServerConfig:
150-
"""Convert client-specific format to ServerConfig
179+
@classmethod
180+
def from_client_format(cls, server_name: str, client_config: Dict[str, Any]) -> ServerConfig:
181+
"""Convert client format to ServerConfig
182+
183+
This is a helper method used by subclasses to convert from client-specific format to ServerConfig.
151184
152185
Args:
153186
server_name: Name of the server
@@ -156,9 +189,56 @@ def _convert_from_client_format(self, server_name: str, client_config: Dict[str,
156189
Returns:
157190
ServerConfig object
158191
"""
159-
# To be implemented by subclasses
160-
raise NotImplementedError("Subclasses must implement _convert_from_client_format")
192+
# Create a dictionary that ServerConfig.from_dict can work with
193+
server_data = {
194+
"name": server_name,
195+
"command": client_config.get("command", ""),
196+
"args": client_config.get("args", []),
197+
}
198+
199+
# Add environment variables if present
200+
if "env" in client_config:
201+
server_data["env_vars"] = client_config["env"]
202+
203+
return ServerConfig.from_dict(server_data)
204+
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)
227+
228+
def list_servers(self) -> List[str]:
229+
"""List all MCP servers in client config
230+
231+
Returns:
232+
List of server names
233+
"""
234+
config = self._load_config()
161235

236+
# Check if mcpServers exists
237+
if "mcpServers" not in config:
238+
return []
239+
240+
return list(config["mcpServers"].keys())
241+
162242
def get_server_configs(self) -> List[ServerConfig]:
163243
"""Get all MCP servers as ServerConfig objects
164244
@@ -194,8 +274,22 @@ def remove_server(self, server_name: str) -> bool:
194274
Returns:
195275
bool: Success or failure
196276
"""
197-
# To be implemented by subclasses
198-
raise NotImplementedError("Subclasses must implement remove_server")
277+
config = self._load_config()
278+
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+
284+
# Check if the server exists
285+
if server_name not in config["mcpServers"]:
286+
logger.warning(f"Server {server_name} not found in {self.display_name} config")
287+
return False
288+
289+
# Remove the server
290+
del config["mcpServers"][server_name]
291+
292+
return self._save_config(config)
199293

200294
def get_client_info(self) -> Dict[str, str]:
201295
"""Get information about this client

src/mcpm/clients/claude_desktop.py

Lines changed: 10 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import os
66
import logging
77
import platform
8-
from typing import Dict, Any, Optional, List
8+
from typing import Dict, Any
99

1010
from mcpm.clients.base import BaseClientManager
11-
from mcpm.utils.server_config import ServerConfig
1211

1312
logger = logging.getLogger(__name__)
1413

@@ -39,122 +38,16 @@ def _get_empty_config(self) -> Dict[str, Any]:
3938
"disabledServers": {}
4039
}
4140

42-
def _add_server_config(self, server_name: str, server_config: Dict[str, Any]) -> bool:
43-
"""Add or update an MCP server in Claude Desktop config using raw config dictionary
41+
# Uses base class implementation of _add_server_config
4442

45-
Note: This is an internal method that should generally not be called directly.
46-
Use add_server with a ServerConfig object instead for better type safety and validation.
47-
48-
Args:
49-
server_name: Name of the server
50-
server_config: Server configuration dictionary
51-
52-
Returns:
53-
bool: Success or failure
54-
"""
55-
config = self._load_config()
56-
57-
# Initialize mcpServers if it doesn't exist
58-
if "mcpServers" not in config:
59-
config["mcpServers"] = {}
60-
61-
# Add or update the server
62-
config["mcpServers"][server_name] = server_config
63-
64-
return self._save_config(config)
65-
66-
def add_server(self, server_config: ServerConfig) -> bool:
67-
"""Add or update a server using a ServerConfig object
68-
69-
This is the preferred method for adding servers as it ensures proper type safety
70-
and validation through the ServerConfig object.
71-
72-
Args:
73-
server_config: ServerConfig object
74-
75-
Returns:
76-
bool: Success or failure
77-
"""
78-
client_config = self._convert_to_client_format(server_config)
79-
return self._add_server_config(server_config.name, client_config)
43+
# Uses base class implementation of add_server
8044

81-
def _convert_to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]:
82-
"""Convert ServerConfig to Claude Desktop format
83-
84-
Args:
85-
server_config: ServerConfig object
86-
87-
Returns:
88-
Dict containing Claude Desktop-specific configuration
89-
"""
90-
# Base result containing essential execution information
91-
result = {
92-
"command": server_config.command,
93-
"args": server_config.args,
94-
}
95-
96-
# Add filtered environment variables if present
97-
non_empty_env = server_config.get_filtered_env_vars()
98-
if non_empty_env:
99-
result["env"] = non_empty_env
100-
101-
# Add additional metadata fields for display in Claude Desktop
102-
# Fields that are None will be automatically excluded by JSON serialization
103-
for field in ["name", "display_name", "description", "installation"]:
104-
value = getattr(server_config, field, None)
105-
if value is not None:
106-
result[field] = value
107-
108-
return result
45+
# Uses the base class implementation of _convert_to_client_format
46+
# which handles the core fields (command, args, env)
10947

110-
@classmethod
111-
def from_claude_desktop_format(cls, server_name: str, client_config: Dict[str, Any]) -> ServerConfig:
112-
"""Convert Claude Desktop format to ServerConfig
113-
114-
Args:
115-
server_name: Name of the server
116-
client_config: Claude Desktop-specific configuration
117-
118-
Returns:
119-
ServerConfig object
120-
"""
121-
# Create a dictionary that ServerConfig.from_dict can work with
122-
server_data = {
123-
"name": server_name,
124-
"command": client_config.get("command", ""),
125-
"args": client_config.get("args", []),
126-
}
127-
128-
# Add environment variables if present
129-
if "env" in client_config:
130-
server_data["env_vars"] = client_config["env"]
131-
132-
# Add additional metadata fields if present
133-
for field in ["display_name", "description", "installation"]:
134-
if field in client_config:
135-
server_data[field] = client_config[field]
136-
137-
return ServerConfig.from_dict(server_data)
48+
# Uses base class implementation via from_client_format
13849

139-
def _convert_from_client_format(self, server_name: str, client_config: Any) -> ServerConfig:
140-
"""Convert Claude Desktop format to ServerConfig
141-
142-
Args:
143-
server_name: Name of the server
144-
client_config: Claude Desktop-specific configuration or ServerConfig object
145-
146-
Returns:
147-
ServerConfig object
148-
"""
149-
# If client_config is already a ServerConfig object, just return it
150-
if isinstance(client_config, ServerConfig):
151-
# Ensure the name is set correctly
152-
if client_config.name != server_name:
153-
client_config.name = server_name
154-
return client_config
155-
156-
# Otherwise, convert from dict format
157-
return self.from_claude_desktop_format(server_name, client_config)
50+
# Uses base class implementation of _convert_from_client_format
15851

15952
def disable_server(self, server_name: str) -> bool:
16053
"""Temporarily disable (stash) a server without removing its configuration
@@ -224,67 +117,8 @@ def is_server_disabled(self, server_name: str) -> bool:
224117
config = self._load_config()
225118
return "disabledServers" in config and server_name in config["disabledServers"]
226119

227-
def remove_server(self, server_name: str) -> bool:
228-
"""Remove an MCP server from Claude Desktop config
229-
230-
Args:
231-
server_name: Name of the server to remove
232-
233-
Returns:
234-
bool: Success or failure
235-
"""
236-
config = self._load_config()
237-
238-
# Check if mcpServers exists
239-
if "mcpServers" not in config:
240-
logger.warning(f"Cannot remove server {server_name}: mcpServers section doesn't exist")
241-
return False
242-
243-
# Check if the server exists
244-
if server_name not in config["mcpServers"]:
245-
logger.warning(f"Server {server_name} not found in Claude Desktop config")
246-
return False
247-
248-
# Remove the server
249-
del config["mcpServers"][server_name]
250-
251-
return self._save_config(config)
120+
# Uses base class implementation of remove_server
252121

253-
def get_server(self, server_name: str) -> Optional[ServerConfig]:
254-
"""Get a server configuration from Claude Desktop
255-
256-
Args:
257-
server_name: Name of the server
258-
259-
Returns:
260-
ServerConfig object if found, None otherwise
261-
"""
262-
config = self._load_config()
263-
264-
# Check if mcpServers exists
265-
if "mcpServers" not in config:
266-
logger.warning(f"Cannot get server {server_name}: mcpServers section doesn't exist")
267-
return None
268-
269-
# Check if the server exists
270-
if server_name not in config["mcpServers"]:
271-
logger.debug(f"Server {server_name} not found in Claude Desktop config")
272-
return None
273-
274-
# Get the server config and convert to StandardServer
275-
client_config = config["mcpServers"][server_name]
276-
return self._convert_from_client_format(server_name, client_config)
122+
# Uses base class implementation of get_server
277123

278-
def list_servers(self) -> List[str]:
279-
"""List all MCP servers in Claude Desktop config
280-
281-
Returns:
282-
List of server names
283-
"""
284-
config = self._load_config()
285-
286-
# Check if mcpServers exists
287-
if "mcpServers" not in config:
288-
return []
289-
290-
return list(config["mcpServers"].keys())
124+
# Uses base class implementation of list_servers

0 commit comments

Comments
 (0)