Skip to content

Commit b8d9b84

Browse files
committed
Fix bug in stash and pop
1 parent 4c45034 commit b8d9b84

File tree

6 files changed

+227
-76
lines changed

6 files changed

+227
-76
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"click>=8.1.3",
1717
"rich>=12.0.0",
1818
"requests>=2.28.0",
19+
"pydantic>=2.5.1",
1920
]
2021

2122
[project.scripts]

src/mcpm/clients/claude_desktop.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,24 @@ def from_claude_desktop_format(cls, server_name: str, client_config: Dict[str, A
136136

137137
return ServerConfig.from_dict(server_data)
138138

139-
def _convert_from_client_format(self, server_name: str, client_config: Dict[str, Any]) -> ServerConfig:
139+
def _convert_from_client_format(self, server_name: str, client_config: Any) -> ServerConfig:
140140
"""Convert Claude Desktop format to ServerConfig
141141
142142
Args:
143143
server_name: Name of the server
144-
client_config: Claude Desktop-specific configuration
144+
client_config: Claude Desktop-specific configuration or ServerConfig object
145145
146146
Returns:
147147
ServerConfig object
148148
"""
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
149157
return self.from_claude_desktop_format(server_name, client_config)
150158

151159
def disable_server(self, server_name: str) -> bool:

src/mcpm/commands/pop.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
"""
2-
Pop command for MCPM - restores previously stashed server configuration
1+
"""Pop command for MCPM - restores previously stashed server configuration
32
"""
43

4+
import logging
55
import click
66
from rich.console import Console
77

88
from mcpm.utils.client_registry import ClientRegistry
9+
from mcpm.utils.config import ConfigManager
910

1011
console = Console()
12+
logger = logging.getLogger(__name__)
1113

1214
@click.command()
1315
@click.argument("server_name")
@@ -32,16 +34,29 @@ def pop(server_name):
3234
console.print("Please switch to a supported client using 'mcpm client <client-name>'")
3335
return
3436

35-
# Check if the server exists in the stashed configurations
36-
if not client_manager.is_server_disabled(server_name):
37-
console.print(f"[bold red]Error:[/] Server '{server_name}' not found in stashed configurations.")
37+
# Access the global config manager
38+
config_manager = ConfigManager()
39+
40+
# Check if the server is stashed for this client
41+
if not config_manager.is_server_stashed(client, server_name):
42+
console.print(f"[bold red]Error:[/] Server '{server_name}' not found in stashed configurations for {client_name}.")
43+
return
44+
45+
# Get the server configuration from global stashed servers
46+
server_data = config_manager.pop_server(client, server_name)
47+
if not server_data:
48+
console.print(f"[bold red]Error:[/] Failed to retrieve stashed configuration for server '{server_name}'.")
3849
return
3950

40-
# Pop (re-enable) the server
41-
success = client_manager.enable_server(server_name)
51+
# Convert the server configuration to the client's format and add it back
52+
# to the active servers
53+
server_config = client_manager._convert_from_client_format(server_name, server_data)
54+
success = client_manager.add_server(server_config)
4255

4356
if success:
4457
console.print(f"[bold green]Restored[/] MCP server '{server_name}' for {client_name}")
4558
console.print("Remember to restart the client for changes to take effect.")
4659
else:
60+
# If adding failed, re-stash the server to avoid data loss
61+
config_manager.stash_server(client, server_name, server_data)
4762
console.print(f"[bold red]Failed to restore[/] '{server_name}' for {client_name}.")

src/mcpm/commands/stash.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
"""
2-
Stash command for MCPM - temporarily stores server configuration aside
1+
"""Stash command for MCPM - temporarily stores server configuration aside
32
"""
43

4+
import logging
55
import click
66
from rich.console import Console
77

88
from mcpm.utils.client_registry import ClientRegistry
9+
from mcpm.utils.config import ConfigManager
910

1011
console = Console()
12+
logger = logging.getLogger(__name__)
1113

1214
@click.command()
1315
@click.argument("server_name")
@@ -33,19 +35,38 @@ def stash(server_name):
3335
return
3436

3537
# Check if the server exists in the active client
36-
server_info = client_manager.get_server(server_name)
37-
if not server_info:
38+
server_config = client_manager.get_server_config(server_name)
39+
if not server_config:
3840
console.print(f"[bold red]Error:[/] Server '{server_name}' not found in {client_name}.")
3941
return
42+
43+
# Convert ServerConfig to dictionary for storage
44+
server_data = server_config.to_dict() if hasattr(server_config, 'to_dict') else server_config
45+
46+
# Access the global config manager
47+
config_manager = ConfigManager()
48+
49+
# Check if server is already stashed
50+
if config_manager.is_server_stashed(client, server_name):
51+
console.print(f"[bold red]Error:[/] Server '{server_name}' is already stashed for {client_name}.")
52+
return
4053

4154
# Display server information before stashing
4255
console.print(f"\n[bold cyan]Stashing server:[/] {server_name}")
4356

44-
# Stash the server (disable it)
45-
success = client_manager.disable_server(server_name)
57+
# Store server in global config's stashed_servers
58+
stash_success = config_manager.stash_server(client, server_name, server_data)
4659

47-
if success:
48-
console.print(f"[bold yellow]Stashed[/] MCP server '{server_name}' for {client_name}")
49-
console.print("Server configuration is stored and can be restored with 'mcpm pop'.")
60+
# Remove the server from the client's configuration
61+
if stash_success:
62+
remove_success = client_manager.remove_server(server_name)
63+
64+
if remove_success:
65+
console.print(f"[bold yellow]Stashed[/] MCP server '{server_name}' for {client_name}")
66+
console.print("Server configuration is stored and can be restored with 'mcpm pop'.")
67+
else:
68+
# If removing failed, also remove from stashed servers to avoid issues
69+
config_manager.pop_server(client, server_name)
70+
console.print(f"[bold red]Failed to remove server from {client_name}. Stashing aborted.")
5071
else:
5172
console.print(f"[bold red]Failed to stash[/] '{server_name}' for {client_name}.")

src/mcpm/utils/config.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,122 @@ def get_supported_clients(self) -> List[str]:
108108
# We'll import here to avoid circular imports
109109
from mcpm.utils.client_registry import ClientRegistry
110110
return ClientRegistry.get_supported_clients()
111+
112+
def stash_server(self, client_name: str, server_name: str, server_config: Any) -> bool:
113+
"""Store a disabled server configuration in the global config
114+
115+
Args:
116+
client_name: Name of the client the server belongs to
117+
server_name: Name of the server to stash
118+
server_config: Server configuration to stash (ServerConfig object or dict)
119+
120+
Returns:
121+
bool: Success or failure
122+
"""
123+
# Ensure stashed_servers section exists
124+
if "stashed_servers" not in self._config:
125+
self._config["stashed_servers"] = {}
126+
127+
# Ensure client section exists
128+
if client_name not in self._config["stashed_servers"]:
129+
self._config["stashed_servers"][client_name] = {}
130+
131+
# Convert ServerConfig to dict if needed
132+
try:
133+
# If it's a ServerConfig object with to_dict method
134+
if hasattr(server_config, 'to_dict') and callable(server_config.to_dict):
135+
server_dict = server_config.to_dict()
136+
else:
137+
# Assume it's already a dict or JSON serializable
138+
server_dict = server_config
139+
140+
# Add the server configuration
141+
self._config["stashed_servers"][client_name][server_name] = server_dict
142+
143+
# Save the config
144+
self._save_config()
145+
return True
146+
except Exception as e:
147+
logger.error(f"Failed to save stashed server: {e}")
148+
return False
149+
150+
def pop_server(self, client_name: str, server_name: str) -> Optional[Dict[str, Any]]:
151+
"""Retrieve a stashed server configuration from the global config
152+
153+
Args:
154+
client_name: Name of the client the server belongs to
155+
server_name: Name of the server to retrieve
156+
157+
Returns:
158+
Dict: Server configuration or None if not found
159+
"""
160+
# Check if stashed_servers section exists
161+
if "stashed_servers" not in self._config:
162+
return None
163+
164+
# Check if client section exists
165+
if client_name not in self._config["stashed_servers"]:
166+
return None
167+
168+
# Check if server exists
169+
if server_name not in self._config["stashed_servers"][client_name]:
170+
return None
171+
172+
# Get the server configuration
173+
server_config = self._config["stashed_servers"][client_name][server_name]
174+
175+
# Remove the server from stashed servers
176+
del self._config["stashed_servers"][client_name][server_name]
177+
178+
# Clean up empty client section if needed
179+
if not self._config["stashed_servers"][client_name]:
180+
del self._config["stashed_servers"][client_name]
181+
182+
# Clean up empty stashed_servers section if needed
183+
if not self._config["stashed_servers"]:
184+
del self._config["stashed_servers"]
185+
186+
# Save the config
187+
self._save_config()
188+
189+
return server_config
190+
191+
def is_server_stashed(self, client_name: str, server_name: str) -> bool:
192+
"""Check if a server is stashed in the global config
193+
194+
Args:
195+
client_name: Name of the client the server belongs to
196+
server_name: Name of the server to check
197+
198+
Returns:
199+
bool: True if server is stashed, False otherwise
200+
"""
201+
# Check if stashed_servers section exists
202+
if "stashed_servers" not in self._config:
203+
return False
204+
205+
# Check if client section exists
206+
if client_name not in self._config["stashed_servers"]:
207+
return False
208+
209+
# Check if server exists
210+
return server_name in self._config["stashed_servers"][client_name]
211+
212+
def get_stashed_servers(self, client_name: str) -> Dict[str, Dict[str, Any]]:
213+
"""Get all stashed servers for a client
214+
215+
Args:
216+
client_name: Name of the client to get stashed servers for
217+
218+
Returns:
219+
Dict: Dictionary of server configurations by name
220+
"""
221+
# Check if stashed_servers section exists
222+
if "stashed_servers" not in self._config:
223+
return {}
224+
225+
# Check if client section exists
226+
if client_name not in self._config["stashed_servers"]:
227+
return {}
228+
229+
return self._config["stashed_servers"][client_name]

0 commit comments

Comments
 (0)