Skip to content

Commit c7a97b3

Browse files
feat: update commands to support profile work scope
1 parent 82f3e48 commit c7a97b3

File tree

22 files changed

+814
-275
lines changed

22 files changed

+814
-275
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ mcpm client --list # List all supported MCP clients and th
5252
5353
mcpm edit # View or edit the active MCP client's configuration file
5454
55-
mcpm list # List all installed MCP servers
55+
mcpm ls # List all installed MCP servers
5656
57-
mcpm remove SERVER_NAME # Remove an installed MCP server
57+
mcpm rm SERVER_NAME # Remove an installed MCP server
5858
5959
mcpm stash SERVER_NAME # Temporarily disable an MCP server for a client
6060
mcpm pop SERVER_NAME # Re-enable an MCP server for a client
6161
62+
mcpm cp SOURCE TARGET # Copy an MCP server from one client/profile to another
63+
mcpm mv SOURCE TARGET # Move an MCP server from one client/profile to another
64+
65+
mcpm activate PROFILE_NAME # Activate a profile
66+
mcpm deactivate # Deactivate the active profile
67+
6268
mcpm inspect SERVER_NAME # Launch the MCPM Inspector UI to examine servers
6369
```
6470

src/mcpm/cli.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
remove,
2121
search,
2222
stash,
23+
transfer,
2324
)
2425

2526
console = Console()
@@ -126,12 +127,16 @@ def main(ctx, help_flag):
126127
"View or edit the active MCPM client's configuration file.",
127128
)
128129
commands_table.add_row(" [cyan]inspector[/]", "Launch the MCPM Inspector UI to examine servers.")
129-
commands_table.add_row(" [cyan]list[/]", "List all installed MCP servers.")
130+
commands_table.add_row(" [cyan]list/ls[/]", "List all installed MCP servers.")
130131
commands_table.add_row(" [cyan]remove[/]", "Remove an installed MCP server.")
131132
commands_table.add_row(" [cyan]search[/]", "Search available MCP servers.")
132133
commands_table.add_row(" [cyan]stash[/]", "Temporarily store a server configuration aside.")
133134
commands_table.add_row(" [cyan]pop[/]", "Restore a previously stashed server configuration.")
134135
commands_table.add_row(" [cyan]profile[/]", "Manage MCPM profiles.")
136+
commands_table.add_row(" [cyan]mv[/]", "Move a server from one client/profile to another.")
137+
commands_table.add_row(" [cyan]cp[/]", "Copy a server from one client/profile to another.")
138+
commands_table.add_row(" [cyan]activate[/]", "Activate a profile.")
139+
commands_table.add_row(" [cyan]deactivate[/]", "Deactivate a profile.")
135140
console.print(commands_table)
136141

137142
# Additional helpful information
@@ -144,6 +149,7 @@ def main(ctx, help_flag):
144149
main.add_command(remove.remove)
145150
main.add_command(add.add)
146151
main.add_command(list.list)
152+
main.add_command(list.list, name="ls")
147153
main.add_command(edit.edit)
148154

149155
main.add_command(stash.stash)
@@ -153,6 +159,10 @@ def main(ctx, help_flag):
153159
main.add_command(config.config)
154160
main.add_command(inspector.inspector, name="inspector")
155161
main.add_command(profile.profile, name="profile")
162+
main.add_command(transfer.move, name="mv")
163+
main.add_command(transfer.copy, name="cp")
164+
main.add_command(profile.activate)
165+
main.add_command(profile.deactivate)
156166

157167
if __name__ == "__main__":
158168
main()

src/mcpm/clients/client_config.py

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
from typing import Any, Dict, List, Optional
77

8+
from mcpm.profile.profile_config import ProfileConfigManager
89
from mcpm.utils.config import ConfigManager
910

1011
logger = logging.getLogger(__name__)
@@ -22,7 +23,7 @@ def _refresh_config(self):
2223
"""Refresh the local config cache from the config manager"""
2324
self._config = self.config_manager.get_config()
2425

25-
def get_active_client(self) -> str:
26+
def get_active_client(self) -> str | None:
2627
"""Get the name of the currently active client or None if not set"""
2728
self._refresh_config()
2829
return self._config.get("active_client")
@@ -56,6 +57,37 @@ def set_active_client(self, client_name: Optional[str]) -> bool:
5657
self._refresh_config()
5758
return result
5859

60+
def get_active_profile(self) -> str | None:
61+
"""Get the name of the currently active profile or None if not set"""
62+
self._refresh_config()
63+
return self._config.get("active_profile")
64+
65+
def set_active_profile(self, profile_name: Optional[str]) -> bool:
66+
"""Set the active profile
67+
68+
Args:
69+
profile_name: Name of profile to set as active, or None to clear
70+
71+
Returns:
72+
bool: Success or failure
73+
"""
74+
# If None, remove the active profile
75+
if profile_name is None:
76+
result = self.config_manager.set_config("active_profile", None)
77+
self._refresh_config()
78+
return result
79+
80+
supported_profiles = ProfileConfigManager().list_profiles()
81+
82+
if profile_name not in supported_profiles:
83+
logger.error(f"Unknown profile: {profile_name}")
84+
return False
85+
86+
# Set the active profile
87+
result = self.config_manager.set_config("active_profile", profile_name)
88+
self._refresh_config()
89+
return result
90+
5991
def get_supported_clients(self) -> List[str]:
6092
"""Get a list of supported client names"""
6193
# Import here to avoid circular imports
@@ -77,11 +109,11 @@ def get_client_manager(self, client_name: str):
77109

78110
return ClientRegistry.get_client_manager(client_name)
79111

80-
def stash_server(self, client_name: str, server_name: str, server_config: Any) -> bool:
112+
def stash_server(self, scope_name: str, server_name: str, server_config: Any) -> bool:
81113
"""Store a disabled server configuration in the global config
82114
83115
Args:
84-
client_name: Name of the client the server belongs to
116+
scope_name: Name of the scope the server belongs to
85117
server_name: Name of the server to stash
86118
server_config: Server configuration to stash (ServerConfig object or dict)
87119
@@ -96,8 +128,8 @@ def stash_server(self, client_name: str, server_name: str, server_config: Any) -
96128
self._config["stashed_servers"] = {}
97129

98130
# Ensure client section exists
99-
if client_name not in self._config["stashed_servers"]:
100-
self._config["stashed_servers"][client_name] = {}
131+
if scope_name not in self._config["stashed_servers"]:
132+
self._config["stashed_servers"][scope_name] = {}
101133

102134
# Convert ServerConfig to dict if needed
103135
try:
@@ -110,9 +142,9 @@ def stash_server(self, client_name: str, server_name: str, server_config: Any) -
110142

111143
# Add the server configuration
112144
stashed_servers = self._config.get("stashed_servers", {})
113-
if client_name not in stashed_servers:
114-
stashed_servers[client_name] = {}
115-
stashed_servers[client_name][server_name] = server_dict
145+
if scope_name not in stashed_servers:
146+
stashed_servers[scope_name] = {}
147+
stashed_servers[scope_name][server_name] = server_dict
116148

117149
# Use set_config to save the updated stashed_servers
118150
result = self.config_manager.set_config("stashed_servers", stashed_servers)
@@ -122,11 +154,11 @@ def stash_server(self, client_name: str, server_name: str, server_config: Any) -
122154
logger.error(f"Failed to save stashed server: {e}")
123155
return False
124156

125-
def pop_server(self, client_name: str, server_name: str) -> Optional[Dict[str, Any]]:
157+
def pop_server(self, scope_name: str, server_name: str) -> Optional[Dict[str, Any]]:
126158
"""Retrieve a stashed server configuration from the global config
127159
128160
Args:
129-
client_name: Name of the client the server belongs to
161+
scope_name: Name of the scope the server belongs to
130162
server_name: Name of the server to retrieve
131163
132164
Returns:
@@ -140,23 +172,23 @@ def pop_server(self, client_name: str, server_name: str) -> Optional[Dict[str, A
140172
if not stashed_servers:
141173
return None
142174

143-
# Check if client section exists
144-
if client_name not in stashed_servers:
175+
# Check if scope section exists
176+
if scope_name not in stashed_servers:
145177
return None
146178

147179
# Check if server exists
148-
if server_name not in stashed_servers[client_name]:
180+
if server_name not in stashed_servers[scope_name]:
149181
return None
150182

151183
# Get the server configuration
152-
server_config = stashed_servers[client_name][server_name]
184+
server_config = stashed_servers[scope_name][server_name]
153185

154186
# Remove the server from stashed servers
155-
del stashed_servers[client_name][server_name]
187+
del stashed_servers[scope_name][server_name]
156188

157-
# Clean up empty client section if needed
158-
if not stashed_servers[client_name]:
159-
del stashed_servers[client_name]
189+
# Clean up empty scope section if needed
190+
if not stashed_servers[scope_name]:
191+
del stashed_servers[scope_name]
160192

161193
# Clean up empty stashed_servers section if needed
162194
if not stashed_servers:
@@ -171,11 +203,11 @@ def pop_server(self, client_name: str, server_name: str) -> Optional[Dict[str, A
171203

172204
return server_config
173205

174-
def is_server_stashed(self, client_name: str, server_name: str) -> bool:
206+
def is_server_stashed(self, scope_name: str, server_name: str) -> bool:
175207
"""Check if a server is stashed in the global config
176208
177209
Args:
178-
client_name: Name of the client the server belongs to
210+
scope_name: Name of the scope the server belongs to
179211
server_name: Name of the server to check
180212
181213
Returns:
@@ -189,18 +221,18 @@ def is_server_stashed(self, client_name: str, server_name: str) -> bool:
189221
if not stashed_servers:
190222
return False
191223

192-
# Check if client section exists
193-
if client_name not in stashed_servers:
224+
# Check if scope section exists
225+
if scope_name not in stashed_servers:
194226
return False
195227

196228
# Check if server exists
197-
return server_name in stashed_servers[client_name]
229+
return server_name in stashed_servers[scope_name]
198230

199-
def get_stashed_servers(self, client_name: str) -> Dict[str, Dict[str, Any]]:
231+
def get_stashed_servers(self, scope_name: str) -> Dict[str, Dict[str, Any]]:
200232
"""Get all stashed servers for a client
201233
202234
Args:
203-
client_name: Name of the client to get stashed servers for
235+
scope_name: Name of the scope to get stashed servers for
204236
205237
Returns:
206238
Dict: Dictionary of server configurations by name
@@ -213,8 +245,8 @@ def get_stashed_servers(self, client_name: str) -> Dict[str, Dict[str, Any]]:
213245
if not stashed_servers:
214246
return {}
215247

216-
# Check if client section exists
217-
if client_name not in stashed_servers:
248+
# Check if scope section exists
249+
if scope_name not in stashed_servers:
218250
return {}
219251

220-
return stashed_servers[client_name]
252+
return stashed_servers[scope_name]

src/mcpm/clients/client_registry.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from mcpm.clients.managers.fiveire import FiveireManager
1818
from mcpm.clients.managers.goose import GooseClientManager
1919
from mcpm.clients.managers.windsurf import WindsurfManager
20+
from mcpm.utils.scope import CLIENT_PREFIX, PROFILE_PREFIX
2021

2122
logger = logging.getLogger(__name__)
2223

@@ -102,12 +103,12 @@ def get_all_client_info(cls) -> Dict[str, Dict[str, str]]:
102103
return {client_name: manager.get_client_info() for client_name, manager in cls._CLIENT_MANAGERS.items()}
103104

104105
@classmethod
105-
def get_active_client(cls) -> str:
106+
def get_active_client(cls) -> str | None:
106107
"""
107108
Get the active client name from the config manager
108109
109110
Returns:
110-
str: Name of the active client
111+
str | None: Name of the active client or None if not set
111112
"""
112113
return cls._client_config_manager.get_active_client()
113114

@@ -165,3 +166,42 @@ def get_supported_clients(cls) -> List[str]:
165166
List[str]: List of supported client names
166167
"""
167168
return list(cls._CLIENT_MANAGERS.keys())
169+
170+
@classmethod
171+
def get_active_profile(cls) -> str | None:
172+
"""
173+
Get the active profile name from the config manager
174+
175+
Returns:
176+
str | None: Name of the active profile or None if not set
177+
"""
178+
return cls._client_config_manager.get_active_profile()
179+
180+
@classmethod
181+
def set_active_profile(cls, profile_name: str | None) -> bool:
182+
"""
183+
Set the active profile in the config manager
184+
185+
Args:
186+
profile_name: Name of the profile or None to unset
187+
188+
Returns:
189+
bool: Success or failure
190+
"""
191+
return cls._client_config_manager.set_active_profile(profile_name)
192+
193+
@classmethod
194+
def determine_active_scope(cls) -> str | None:
195+
"""
196+
Determine the active scope (client or profile) based on config
197+
198+
Returns:
199+
str | None: Name of the active client or profile, or None if not set
200+
"""
201+
profile = cls.get_active_profile()
202+
if profile:
203+
return f"{PROFILE_PREFIX}{profile}"
204+
client = cls.get_active_client()
205+
if client:
206+
return f"{CLIENT_PREFIX}{client}"
207+
return None

src/mcpm/clients/managers/fiveire.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ def get_server(self, server_name: str) -> Optional[ServerConfig]:
116116
return None
117117

118118
# Get the server config and convert to ServerConfig
119-
client_config = servers[server_name]
120-
return self.from_client_format(server_name, client_config)
119+
return servers[server_name]
121120

122121
def add_server(self, server_config: Union[ServerConfig, Dict[str, Any]], name: Optional[str] = None) -> bool:
123122
"""Add or update a server in the client config

src/mcpm/commands/__init__.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@
22
MCPM commands package
33
"""
44

5-
__all__ = ["add", "client", "edit", "inspector", "list", "pop", "profile", "remove", "search", "stash"]
5+
__all__ = [
6+
"add",
7+
"client",
8+
"edit",
9+
"inspector",
10+
"list",
11+
"pop",
12+
"profile",
13+
"remove",
14+
"search",
15+
"stash",
16+
"transfer",
17+
]
618

719
# All command modules
8-
from . import add, client, edit, inspector, list, pop, profile, remove, search, stash
20+
from . import client, edit, inspector, list, profile, search
21+
from .server_operations import add, pop, remove, stash, transfer

src/mcpm/commands/edit.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ def edit():
3434
# Get the active client manager and related information
3535
client_manager = ClientRegistry.get_active_client_manager()
3636
client = ClientRegistry.get_active_client()
37+
if client is None:
38+
print_client_error()
39+
return
3740
client_info = ClientRegistry.get_client_info(client)
3841
client_name = client_info.get("name", client)
3942

4043
# Check if client is supported
4144
if client_manager is None:
42-
print_client_error(client_name)
45+
print_client_error()
4346
return
4447

4548
# Get the client config file path
@@ -110,7 +113,7 @@ def edit():
110113
if server_count > 0:
111114
console.print("\n[bold]MCP Server Details:[/]")
112115
for server_name, server_config in config_json.get("mcpServers", {}).items():
113-
print_server_config(server_name, server_config)
116+
print_server_config(client_manager.from_client_format(server_name, server_config))
114117

115118
except json.JSONDecodeError:
116119
console.print("[yellow]Warning: Config file contains invalid JSON[/]")

0 commit comments

Comments
 (0)