diff --git a/src/mcpm/clients/base.py b/src/mcpm/clients/base.py index 0cb511a4..8f1a53fe 100644 --- a/src/mcpm/clients/base.py +++ b/src/mcpm/clients/base.py @@ -7,6 +7,7 @@ import logging import os import platform +import re from typing import Any, Dict, List, Optional, Union from pydantic import TypeAdapter @@ -158,6 +159,35 @@ def deactivate_profile(self) -> bool: """ pass + def get_associated_profile(self) -> Optional[str]: + """ + Get the associated profile for this client + + Returns: + Optional[str]: Name of the associated profile, or None if no profile is associated + """ + router_service = self.get_server(ROUTER_SERVER_NAME) + if not router_service: + # No associated profile + return None + + # Extract profile name from router service + if isinstance(router_service, STDIOServerConfig): + if hasattr(router_service, "args") and "--headers" in router_service.args: + try: + idx = router_service.args.index("profile") + if idx < len(router_service.args) - 1: + return router_service.args[idx + 1] + except ValueError: + pass + else: + if hasattr(router_service, "url") and "profile=" in router_service.url: + matched = re.search(r"profile=([^&]+)", router_service.url) + if matched: + return matched.group(1) + + return None + class JSONClientManager(BaseClientManager): """ diff --git a/src/mcpm/clients/client_config.py b/src/mcpm/clients/client_config.py index 2a83b36d..02a8c569 100644 --- a/src/mcpm/clients/client_config.py +++ b/src/mcpm/clients/client_config.py @@ -54,6 +54,9 @@ def set_active_client(self, client_name: Optional[str]) -> bool: # Set the active client result = self.config_manager.set_config("active_client", client_name) + # refresh the active profile + client = ClientRegistry.get_client_manager(client_name) + self.set_active_profile(client.get_associated_profile()) # type: ignore self._refresh_config() return result diff --git a/src/mcpm/clients/managers/claude_desktop.py b/src/mcpm/clients/managers/claude_desktop.py index 384c25ac..77a2dd0d 100644 --- a/src/mcpm/clients/managers/claude_desktop.py +++ b/src/mcpm/clients/managers/claude_desktop.py @@ -8,7 +8,7 @@ from mcpm.clients.base import JSONClientManager from mcpm.schemas.server_config import ServerConfig -from mcpm.utils.router_server import format_server_url_with_proxy_param +from mcpm.utils.router_server import format_server_url_with_proxy_headers logger = logging.getLogger(__name__) @@ -114,7 +114,7 @@ def is_server_disabled(self, server_name: str) -> bool: return "disabledServers" in config and server_name in config["disabledServers"] def _format_router_server(self, profile_name, base_url) -> ServerConfig: - return format_server_url_with_proxy_param(self.client_key, profile_name, base_url) + return format_server_url_with_proxy_headers(self.client_key, profile_name, base_url) # Uses base class implementation of remove_server diff --git a/src/mcpm/clients/managers/continue_extension.py b/src/mcpm/clients/managers/continue_extension.py index deb5d666..cf30b800 100644 --- a/src/mcpm/clients/managers/continue_extension.py +++ b/src/mcpm/clients/managers/continue_extension.py @@ -43,12 +43,10 @@ def __init__(self, config_path=None): self.config_path = config_path else: # Set config path based on detected platform - if os.name == "Darwin": # macOS - self.config_path = os.path.expanduser("~/.continue/config.yaml") - elif os.name == "Windows": + if self._system == "Windows": self.config_path = os.path.join(os.environ.get("USERPROFILE", ""), ".continue", "config.yaml") else: - # Linux + # MacOS or Linux self.config_path = os.path.expanduser("~/.continue/config.yaml") # Also check for workspace config diff --git a/src/mcpm/clients/managers/cursor.py b/src/mcpm/clients/managers/cursor.py index 6432747d..bb06752e 100644 --- a/src/mcpm/clients/managers/cursor.py +++ b/src/mcpm/clients/managers/cursor.py @@ -31,13 +31,11 @@ def __init__(self, config_path=None): self.config_path = config_path else: # Set config path based on detected platform - if self._system == "Darwin": # macOS - self.config_path = os.path.expanduser("~/Library/Application Support/Cursor/User/mcp_config.json") - elif self._system == "Windows": - self.config_path = os.path.join(os.environ.get("APPDATA", ""), "Cursor", "User", "mcp_config.json") + if self._system == "Windows": + self.config_path = os.path.join(os.environ.get("USERPROFILE", ""), ".cursor", "mcp.json") else: - # Linux - self.config_path = os.path.expanduser("~/.config/Cursor/User/mcp_config.json") + # MacOS or Linux + self.config_path = os.path.expanduser("~/.cursor/mcp.json") def _get_empty_config(self) -> Dict[str, Any]: """Get empty config structure for Cursor""" diff --git a/src/mcpm/clients/managers/goose.py b/src/mcpm/clients/managers/goose.py index 4ac5a07c..d997d7e3 100644 --- a/src/mcpm/clients/managers/goose.py +++ b/src/mcpm/clients/managers/goose.py @@ -41,12 +41,10 @@ def __init__(self, config_path=None): self.config_path = config_path else: # Set config path based on detected platform - if os.name == "Darwin": # macOS - self.config_path = os.path.expanduser("~/.config/goose/config.yaml") - elif os.name == "Windows": + if self._system == "Windows": self.config_path = os.path.join(os.environ.get("USERPROFILE", ""), ".config", "goose", "config.yaml") else: - # Linux + # MacOS or Linux self.config_path = os.path.expanduser("~/.config/goose/config.yaml") # Also check for workspace config diff --git a/src/mcpm/clients/managers/windsurf.py b/src/mcpm/clients/managers/windsurf.py index 45a7a56b..a33514cf 100644 --- a/src/mcpm/clients/managers/windsurf.py +++ b/src/mcpm/clients/managers/windsurf.py @@ -32,14 +32,12 @@ def __init__(self, config_path=None): self.config_path = config_path else: # Set config path based on detected platform - if self._system == "Darwin": # macOS - self.config_path = os.path.expanduser("~/.codeium/windsurf/mcp_config.json") - elif self._system == "Windows": + if self._system == "Windows": self.config_path = os.path.join( - os.environ.get("LOCALAPPDATA", ""), "Codeium", "windsurf", "mcp_config.json" + os.environ.get("USERPROFILE", ""), ".codeium", "windsurf", "mcp_config.json" ) else: - # Linux + # MacOS or Linux self.config_path = os.path.expanduser("~/.codeium/windsurf/mcp_config.json") def to_client_format(self, server_config: ServerConfig) -> Dict[str, Any]: diff --git a/src/mcpm/commands/client.py b/src/mcpm/commands/client.py index 7ec4b18b..7129c6c6 100644 --- a/src/mcpm/commands/client.py +++ b/src/mcpm/commands/client.py @@ -46,6 +46,7 @@ def list_clients(): table.add_column("Client Name", style="cyan") table.add_column("Installation", style="yellow") table.add_column("Status", style="green") + table.add_column("Profile", style="magenta") active_client = ClientRegistry.get_active_client() installed_clients = ClientRegistry.detect_installed_clients() @@ -61,8 +62,12 @@ def list_clients(): # Get client info for more details client_info = ClientRegistry.get_client_info(client) display_name = client_info.get("name", client) + # Get Profile activated + client_manager = ClientRegistry.get_client_manager(client) + profile = client_manager.get_associated_profile() if client_manager else None + active_profile = f"[bold magenta]{profile}[/]" if profile else "" - table.add_row(f"{display_name} ({client})", install_status, active_status) + table.add_row(f"{display_name} ({client})", install_status, active_status, active_profile) console.print(table) @@ -83,6 +88,7 @@ def set_client(client_name): CLIENT is the name of the client to set as active. """ + # Get the list of supported clients supported_clients = ClientRegistry.get_supported_clients() @@ -97,10 +103,13 @@ def set_client(client_name): console.print(f"[bold yellow]Note:[/] {client_name} is already the active client") return - # Attempt to set the active client + # Attempt to set the active client with active profile inner switched success = ClientRegistry.set_active_client(client_name) if success: console.print(f"[bold green]Success:[/] Active client set to {client_name}") + active_profile = ClientRegistry.get_active_profile() + if active_profile: + console.print(f"[bold green]Success:[/] Active profile set to {active_profile}") # Provide information about what this means panel = Panel( diff --git a/src/mcpm/commands/profile.py b/src/mcpm/commands/profile.py index d608d8fb..cf46c53f 100644 --- a/src/mcpm/commands/profile.py +++ b/src/mcpm/commands/profile.py @@ -36,7 +36,12 @@ def activate(profile_name, client=None): client_registry = ClientRegistry() config_manager = ConfigManager() + activate_this_client: bool = client is None + if client: + if client == ClientRegistry.get_active_client(): + activate_this_client = True + console.print(f"[bold cyan]Activating profile '{profile_name}' in client '{client}'...[/]") client_manager = ClientRegistry.get_client_manager(client) if client_manager is None: @@ -54,9 +59,11 @@ def activate(profile_name, client=None): console.print(f"[bold red]Error:[/] Client '{client}' not found.") return success = client_manager.activate_profile(profile_name, config_manager.get_router_config()) - if success: + if success and activate_this_client: client_registry.set_active_profile(profile_name) console.print(f"\n[green]Profile '{profile_name}' activated successfully.[/]\n") + elif success: + console.print(f"\n[green]Profile '{profile_name}' activated successfully for client '{client}'.[/]\n") else: console.print(f"[bold red]Error:[/] Failed to activate profile '{profile_name}'.") @@ -69,15 +76,20 @@ def deactivate(client=None): Unsets the active profile. """ + deactivate_this_client: bool = client is None + # Set the active profile active_profile = ClientRegistry.get_active_profile() - if active_profile is None: + if deactivate_this_client and active_profile is None: console.print("[bold yellow]No active profile found.[/]\n") return console.print(f"\n[green]Deactivating profile '{active_profile}'...[/]") client_registry = ClientRegistry() if client: + if client == ClientRegistry.get_active_client(): + deactivate_this_client = True + console.print(f"[bold cyan]Deactivating profile '{active_profile}' in client '{client}'...[/]") client_manager = ClientRegistry.get_client_manager(client) if client_manager is None: @@ -95,9 +107,11 @@ def deactivate(client=None): console.print(f"[bold red]Error:[/] Client '{client}' not found.") return success = client_manager.deactivate_profile() - if success: + if success and deactivate_this_client: client_registry.set_active_profile(None) console.print(f"\n[yellow]Profile '{active_profile}' deactivated successfully.[/]\n") + elif success: + console.print(f"\n[yellow]Profile '{active_profile}' deactivated successfully for client '{client}'.[/]\n") else: console.print(f"[bold red]Error:[/] Failed to deactivate profile '{active_profile}' in client '{client}'.") @@ -197,6 +211,22 @@ def remove(profile_name): if not profile_config_manager.delete_profile(profile_name): console.print(f"[bold red]Error:[/] Profile '{profile_name}' not found.") return + # Check whether any client is associated with the deleted profile + clients = ClientRegistry.get_supported_clients() + for client in clients: + client_manager = ClientRegistry.get_client_manager(client) + if client_manager: + profile_this_client_associated = client_manager.get_associated_profile() + if profile_this_client_associated == profile_name: + # Deactivate the profile in this client + client_manager.deactivate_profile() + console.print(f"\n[green]Profile '{profile_name}' deactivated successfully for client '{client}'.[/]\n") + + # fresh the active_profile + activated_profile = ClientRegistry.get_active_profile() + if activated_profile == profile_name: + ClientRegistry.set_active_profile(None) + console.print(f"\n[green]Profile '{profile_name}' deleted successfully.[/]\n") @@ -212,4 +242,22 @@ def rename(profile_name): if not profile_config_manager.rename_profile(profile_name, new_profile_name): console.print(f"[bold red]Error:[/] Profile '{profile_name}' not found.") return + # Check whether any client is associated with the profile to be renamed + clients = ClientRegistry.get_supported_clients() + config_manager = ConfigManager() + for client in clients: + client_manager = ClientRegistry.get_client_manager(client) + if client_manager: + profile_this_client_associated = client_manager.get_associated_profile() + if profile_this_client_associated == profile_name: + # fresh the config + client_manager.deactivate_profile() + client_manager.activate_profile(new_profile_name, config_manager.get_router_config()) + console.print(f"\n[green]Profile '{profile_name}' deactivated successfully for client '{client}'.[/]\n") + + # fresh the active_profile + activated_profile = ClientRegistry.get_active_profile() + if activated_profile == profile_name: + ClientRegistry.set_active_profile(new_profile_name) + console.print(f"\n[green]Profile '{profile_name}' renamed to '{new_profile_name}' successfully.[/]\n") diff --git a/src/mcpm/commands/server_operations/common.py b/src/mcpm/commands/server_operations/common.py index 0fe0a421..5e50ec21 100644 --- a/src/mcpm/commands/server_operations/common.py +++ b/src/mcpm/commands/server_operations/common.py @@ -57,6 +57,7 @@ def profile_add_server(profile: str, server_config: ServerConfig, force: bool = if profile_manager.get_profile(profile) is None: console.print(f"[bold red]Error:[/] Profile '{profile}' not found.") return False + if profile_manager.get_profile_server(profile, server_config.name) and not force: console.print(f"[bold red]Error:[/] Server '{server_config.name}' already exists in {profile}.") console.print("Use --force to override.")