diff --git a/README.md b/README.md index 4b951fe..cb12ff6 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Or choose [other installation methods](#-other-installation-methods) like `brew` MCPM simplifies the installation, configuration, and management of Model Context Protocol servers and their configurations across different applications (clients). Key features include: - ✨ Easy addition and removal of MCP server configurations for supported clients. -- 📋 Centralized management using profiles: group server configurations together and activate/deactivate them easily. +- 📋 Centralized management using profiles: group server configurations together and add/remove them to client easily. - 🔍 Discovery of available MCP servers through a central registry. - 🔌 MCPM Router for aggregating multiple MCP servers behind a single endpoint with shared sessions. - 💻 A command-line interface (CLI) for all management tasks. @@ -78,7 +78,6 @@ mcpm --version # Display the current version of MCPM ```bash mcpm client ls # List all supported MCP clients, detect installed ones, and show active client -mcpm client set CLIENT # Set the active client for subsequent commands mcpm client edit # Open the active client's MCP configuration file in an external editor ``` @@ -114,7 +113,7 @@ mcpm pop [SERVER_NAME] # Restore the last stashed server, or a specific one b Profiles are named collections of server configurations. They allow you to easily switch between different sets of MCP servers. For example, you might have a `work` profile and a `personal` profile, each containing different servers. Or you might have a `production` profile and a `development` profile, each containing different configurations for the same servers. -The currently *active* profile's servers are typically used by features like the MCPM Router. Use `mcpm activate` to set the active profile. +The currently *active* profile's servers are typically used by features like the MCPM Router. Use `mcpm target set %profile_name` to set the active profile. ```bash # 🔄 Profile Lifecycle @@ -122,17 +121,14 @@ mcpm profile ls # List all available MCPM profiles mcpm profile add PROFILE_NAME # Add a new, empty profile mcpm profile rm PROFILE_NAME # Remove a profile (does not delete servers within it) mcpm profile rename OLD_NAME NEW_NAME # Rename a profile - -# ✅ Activating Profiles -mcpm activate PROFILE_NAME # Activate a profile, applying its servers to the active client -mcpm deactivate # Deactivate the current profile for the active client +mcpm add %profile_name # Add a profile to the active client ``` ### 🔌 Router Management (`router`) The MCPM Router runs as a background daemon process, acting as a stable endpoint (e.g., `http://localhost:6276`) that intelligently routes incoming MCP requests to the appropriate server based on the currently **active profile**. -This allows you to change the underlying servers (by switching profiles with `mcpm activate`) without reconfiguring your client applications. They can always point to the MCPM Router's address. +This allows you to change the underlying servers (by switching profiles with `mcpm target set %profile_name`) without reconfiguring your client applications. They can always point to the MCPM Router's address. The Router also maintains persistent connections to MCP servers, enabling multiple clients to share these server sessions. This eliminates the need to start separate server instances for each client, significantly reducing resource usage and startup time. Learn more about these advanced capabilities in [Advanced Features](docs/advanced_features.md). @@ -168,7 +164,7 @@ The MCP Registry is a central repository of available MCP servers that can be in - [x] Basic server management (`mcpm add`, `mcpm ls`, `mcpm rm`) - [x] Registry integration (`mcpm search`, adding by name) - [x] Router functionality (`mcpm router`) -- [x] MCP Profiles (`mcpm profile`, `mcpm activate/deactivate`) +- [x] MCP Profiles (`mcpm profile`) - [x] Server copying/moving (`mcpm cp`, `mcpm mv`) - [x] Server stashing (`mcpm stash`, `mcpm pop`) - [x] Router remote share (`mcpm router share`) remotely access local router and mcp servers diff --git a/README.zh-CN.md b/README.zh-CN.md index 62d84ce..95490df 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -112,7 +112,6 @@ mcpm --version # 显示 MCPM 的当前版本 ```bash mcpm client ls # 列出所有支持的 MCP 客户端,检测已安装的客户端,并显示活动客户端 -mcpm client set CLIENT # 为后续命令设置活动客户端 mcpm client edit # 在外部编辑器中打开活动客户端的 MCP 配置文件 ``` @@ -148,7 +147,7 @@ mcpm pop [SERVER_NAME] # 恢复最后暂存的服务器,或按名称恢复 配置文件是服务器配置的命名集合。它们允许您轻松切换不同的 MCP 服务器集。例如,您可能有一个 `work` 配置文件和一个 `personal` 配置文件,每个都包含不同的服务器。或者,您可能有一个 `production` 配置文件和一个 `development` 配置文件,每个都包含同一服务器的不同配置。 -当前*活动*配置文件的服务器通常由 MCPM 路由器等功能使用。使用 `mcpm activate` 设置活动配置文件。 +当前*活动*配置文件的服务器通常由 MCPM 路由器等功能使用。使用 `mcpm target set %profile_name` 设置活动配置文件。 ```bash # 🔄 配置文件生命周期 @@ -156,17 +155,13 @@ mcpm profile ls # 列出所有可用的 MCPM 配置文件 mcpm profile add PROFILE_NAME # 添加新的空配置文件 mcpm profile rm PROFILE_NAME # 删除配置文件(不删除其中的服务器) mcpm profile rename OLD_NAME NEW_NAME # 重命名配置文件 - -# ✅ 激活配置文件 -mcpm activate PROFILE_NAME # 激活配置文件,将其服务器应用于活动客户端 -mcpm deactivate # 为活动客户端停用当前配置文件 ``` ### 🔌 路由器管理 (`router`) MCPM 路由器作为后台守护进程运行,充当稳定端点(例如 `http://localhost:6276`),根据当前**活动配置文件**智能地将传入的 MCP 请求路由到适当的服务器。 -这允许您通过切换配置文件(使用 `mcpm activate`)来更改底层服务器,而无需重新配置客户端应用程序。它们可以始终指向 MCPM 路由器的地址。 +这允许您通过切换配置文件(使用 `mcpm target set %profile_name`)来更改底层服务器,而无需重新配置客户端应用程序。它们可以始终指向 MCPM 路由器的地址。 路由器还维护与 MCP 服务器的持久连接,使多个客户端能够共享这些服务器会话。这消除了为每个客户端启动单独服务器实例的需要,显著减少资源使用和启动时间。在 [高级功能](docs/advanced_features.md) 中了解有关这些高级功能的更多信息。 @@ -202,7 +197,7 @@ MCP 注册表是可使用 MCPM 安装的可用 MCP 服务器的中央存储库 - [x] 基本服务器管理 (`mcpm add`, `mcpm ls`, `mcpm rm`) - [x] 注册表集成 (`mcpm search`, 按名称添加) - [x] 路由器功能 (`mcpm router`) -- [x] MCP 配置文件 (`mcpm profile`, `mcpm activate/deactivate`) +- [x] MCP 配置文件 (`mcpm profile`) - [x] 服务器复制/移动 (`mcpm cp`, `mcpm mv`) - [x] 服务器暂存 (`mcpm stash`, `mcpm pop`) - [x] 路由器远程分享 (`mcpm router share`) 远程访问本地路由器和 MCP 服务器 diff --git a/pages/index.html b/pages/index.html index 2b1ebad..f61928b 100644 --- a/pages/index.html +++ b/pages/index.html @@ -871,14 +871,6 @@

-
-

Activate Profiles

-

Switch between different sets of server configurations:

-
- $ mcpm activate work - -
-

List Profiles

@@ -1013,10 +1005,10 @@

Client Management

Set Active Client

-

Easily switch between and manage MCP clients:

+

Easily switch between and manage MCP clients/profiles:

- $ mcpm client set claude-desktop - + $ mcpm target set @claude-desktop +
@@ -1100,10 +1092,10 @@

Supported Clients

" add my-server", " ls", " profile add work", - " activate work", + " target set %work", " router on", " stash my-server", - " client set claude" + " target set @claude-desktop" ]; let currentCommand = 0; let charIndex = 0; diff --git a/src/mcpm/cli.py b/src/mcpm/cli.py index 97ec186..8e3196f 100644 --- a/src/mcpm/cli.py +++ b/src/mcpm/cli.py @@ -22,6 +22,7 @@ router, search, stash, + target, transfer, ) @@ -94,39 +95,45 @@ def main(ctx, help_flag, version): return # Check if a command is being executed (and it's not help, no command, or the client command) - if ctx.invoked_subcommand and ctx.invoked_subcommand != "client" and not help_flag: + if ( + ctx.invoked_subcommand + and ctx.invoked_subcommand not in ["target", "client", "profile", "router"] + and not help_flag + ): # Check if active client is set - active_client = client_config_manager.get_active_client() - if not active_client: - console.print("[bold red]Error:[/] No active client set.") - console.print("Please run 'mcpm client set ' to set an active client.") - console.print("Available clients:") + active_target = client_config_manager.get_active_target() + if not active_target: + console.print("[bold red]Error:[/] No active target set.") + console.print("Please run 'mcpm target set ' to set an active target\n") # Show available clients from mcpm.clients.client_registry import ClientRegistry + console.print("[bold green]Available Clients, set one with 'mcpm target set @':[/]") for client in ClientRegistry.get_supported_clients(): console.print(f" - {client}") + from mcpm.profile.profile_config import ProfileConfigManager + + # Show available profiles + console.print("[bold green]Available Profiles, set one with 'mcpm target set %':[/]") + profile_manager = ProfileConfigManager() + for profile in profile_manager.list_profiles(): + console.print(f" - {profile}") + # Exit with error ctx.exit(1) # If no command was invoked or help is requested, show our custom help if ctx.invoked_subcommand is None or help_flag: # Get active client - active_client = client_config_manager.get_active_client() + active_target = client_config_manager.get_active_target() print_logo() - # Get information about installed clients - from mcpm.clients.client_registry import ClientRegistry - - installed_clients = ClientRegistry.detect_installed_clients() - # Display active client information and main help - if active_client: - client_status = "[green]✓[/]" if installed_clients.get(active_client, False) else "[yellow]⚠[/]" - console.print(f"[bold magenta]Active client:[/] [yellow]{active_client}[/] {client_status}") + if active_target: + console.print(f"[bold magenta]Active target:[/] [yellow]{active_target}[/]") else: - console.print("[bold red]No active client set![/] Please run 'mcpm client set ' to set one.") + console.print("[bold red]No active target set![/] Please run 'mcpm target set ' to set one.") console.print("") # Display usage info @@ -144,8 +151,12 @@ def main(ctx, help_flag, version): # Display available commands in a table console.print("[bold]Commands:[/]") commands_table = Table(show_header=False, box=None, padding=(0, 2, 0, 0)) + + commands_table.add_row("[yellow]work target[/]") + commands_table.add_row(" [cyan]target[/]", "Manage the active MCPM target.") + commands_table.add_row("[yellow]client[/]") - commands_table.add_row(" [cyan]client[/]", "Manage the active MCPM client.") + commands_table.add_row(" [cyan]client[/]", "Manage supported MCPM clients.") commands_table.add_row("[yellow]server[/]") commands_table.add_row(" [cyan]search[/]", "Search available MCP servers.") @@ -161,8 +172,6 @@ def main(ctx, help_flag, version): commands_table.add_row("[yellow]profile[/]") commands_table.add_row(" [cyan]profile[/]", "Manage MCPM profiles.") - commands_table.add_row(" [cyan]activate[/]", "Activate a profile.") - commands_table.add_row(" [cyan]deactivate[/]", "Deactivate a profile.") commands_table.add_row("[yellow]router[/]") commands_table.add_row(" [cyan]router[/]", "Manage MCP router service.") @@ -187,14 +196,13 @@ def main(ctx, help_flag, version): main.add_command(stash.stash) main.add_command(pop.pop) +main.add_command(target.target) main.add_command(client.client) main.add_command(config.config) main.add_command(inspector.inspector, name="inspector") main.add_command(profile.profile, name="profile") main.add_command(transfer.move, name="mv") main.add_command(transfer.copy, name="cp") -main.add_command(profile.activate) -main.add_command(profile.deactivate) main.add_command(router.router, name="router") main.add_command(custom.import_server, name="import") diff --git a/src/mcpm/clients/client_config.py b/src/mcpm/clients/client_config.py index 02a8c56..53722c1 100644 --- a/src/mcpm/clients/client_config.py +++ b/src/mcpm/clients/client_config.py @@ -5,8 +5,8 @@ import logging from typing import Any, Dict, List, Optional -from mcpm.profile.profile_config import ProfileConfigManager from mcpm.utils.config import ConfigManager +from mcpm.utils.scope import ScopeType, extract_from_scope logger = logging.getLogger(__name__) @@ -24,72 +24,41 @@ def _refresh_config(self): self._config = self.config_manager.get_config() def get_active_client(self) -> str | None: - """Get the name of the currently active client or None if not set""" - self._refresh_config() - return self._config.get("active_client") + target = self.get_active_target() + if not target: + return + scope_type, scope = extract_from_scope(target) + if scope_type != ScopeType.CLIENT: + return + return scope - def set_active_client(self, client_name: Optional[str]) -> bool: - """Set the active client + def set_active_target(self, target_name: str | None) -> bool: + """Set the active target Args: - client_name: Name of client to set as active, or None to clear + target_name: Name of target to set as active Returns: bool: Success or failure """ - # If None, remove the active client - if client_name is None: - result = self.config_manager.set_config("active_client", None) - self._refresh_config() - return result - - # Get supported clients - from mcpm.clients.client_registry import ClientRegistry - - supported_clients = ClientRegistry.get_supported_clients() - - if client_name not in supported_clients: - logger.error(f"Unknown client: {client_name}") - return False - - # 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 + # Set the active target + result = self.config_manager.set_config("active_target", target_name) self._refresh_config() return result - def get_active_profile(self) -> str | None: - """Get the name of the currently active profile or None if not set""" + def get_active_target(self) -> str | None: + """Get the name of the currently active target or None if not set""" self._refresh_config() - return self._config.get("active_profile") - - def set_active_profile(self, profile_name: Optional[str]) -> bool: - """Set the active profile - - Args: - profile_name: Name of profile to set as active, or None to clear + return self._config.get("active_target") - Returns: - bool: Success or failure - """ - # If None, remove the active profile - if profile_name is None: - result = self.config_manager.set_config("active_profile", None) - self._refresh_config() - return result - - supported_profiles = ProfileConfigManager().list_profiles() - - if profile_name not in supported_profiles: - logger.error(f"Unknown profile: {profile_name}") - return False - - # Set the active profile - result = self.config_manager.set_config("active_profile", profile_name) - self._refresh_config() - return result + def get_active_profile(self) -> str | None: + target = self.get_active_target() + if not target: + return + scope_type, scope = extract_from_scope(target) + if scope_type != ScopeType.PROFILE: + return + return scope def get_supported_clients(self) -> List[str]: """Get a list of supported client names""" diff --git a/src/mcpm/clients/client_registry.py b/src/mcpm/clients/client_registry.py index 2fa0845..6341fd1 100644 --- a/src/mcpm/clients/client_registry.py +++ b/src/mcpm/clients/client_registry.py @@ -18,8 +18,6 @@ from mcpm.clients.managers.goose import GooseClientManager from mcpm.clients.managers.trae import TraeManager from mcpm.clients.managers.windsurf import WindsurfManager -from mcpm.utils.config import ConfigManager -from mcpm.utils.scope import CLIENT_PREFIX, PROFILE_PREFIX logger = logging.getLogger(__name__) @@ -115,19 +113,6 @@ def get_active_client(cls) -> str | None: """ return cls._client_config_manager.get_active_client() - @classmethod - def set_active_client(cls, client_name: str) -> bool: - """ - Set the active client in the config manager - - Args: - client_name: Name of the client - - Returns: - bool: Success or failure - """ - return cls._client_config_manager.set_active_client(client_name) - @classmethod def get_active_client_manager(cls) -> Optional[BaseClientManager]: """ @@ -173,72 +158,32 @@ def get_supported_clients(cls) -> List[str]: @classmethod def get_active_profile(cls) -> str | None: """ - Get the active profile name from the config manager + Get the active profile from the config manager Returns: - str | None: Name of the active profile or None if not set + str | None: Name of the active profile, or None if not set """ return cls._client_config_manager.get_active_profile() @classmethod - def set_active_profile(cls, profile_name: str | None) -> bool: - """ - Set the active profile in the config manager - - Args: - profile_name: Name of the profile or None to unset - - Returns: - bool: Success or failure - """ - return cls._client_config_manager.set_active_profile(profile_name) - - @classmethod - def determine_active_scope(cls) -> str | None: + def get_active_target(cls) -> str | None: """ - Determine the active scope (client or profile) based on config + Get the active target (client or profile) from the config manager Returns: str | None: Name of the active client or profile, or None if not set """ - profile = cls.get_active_profile() - if profile: - return f"{PROFILE_PREFIX}{profile}" - client = cls.get_active_client() - if client: - return f"{CLIENT_PREFIX}{client}" - return None - - @classmethod - def activate_profile(cls, client_name: str, profile_name: str) -> bool: - """ - Activate a profile in the client config - - Args: - client_name: Name of the client - profile_name: Name of the profile - - Returns: - bool: Success or failure - """ - router_config = ConfigManager().get_router_config() - client = cls.get_client_manager(client_name) - if client is None: - return False - return client.activate_profile(profile_name, router_config) + return cls._client_config_manager.get_active_target() @classmethod - def deactivate_profile(cls, client_name: str) -> bool: + def set_active_target(cls, target: str | None) -> bool: """ - Deactivate a profile in the client config + Set the active target (client or profile) in the config manager Args: - profile_name: Name of the profile + target: Name of the client or profile Returns: bool: Success or failure """ - client = cls.get_client_manager(client_name) - if client is None: - return False - return client.deactivate_profile() + return cls._client_config_manager.set_active_target(target) diff --git a/src/mcpm/commands/__init__.py b/src/mcpm/commands/__init__.py index 0281930..96c4d6a 100644 --- a/src/mcpm/commands/__init__.py +++ b/src/mcpm/commands/__init__.py @@ -20,5 +20,5 @@ # All command modules -from . import client, inspector, list, profile, router, search -from .server_operations import add, custom, pop, remove, stash, transfer +from . import client, inspector, list, profile, router, search, target +from .target_operations import add, custom, pop, remove, stash, transfer diff --git a/src/mcpm/commands/client.py b/src/mcpm/commands/client.py index b6dbde4..6f9ce01 100644 --- a/src/mcpm/commands/client.py +++ b/src/mcpm/commands/client.py @@ -30,7 +30,6 @@ def client(): \b mcpm client ls # List all supported MCP clients and their status - mcpm client set CLIENT # Set the active client mcpm client edit # Open active client MCP settings in external editor """ pass @@ -81,61 +80,18 @@ def list_clients(): console.print(f"[yellow]{info.get('name', client)}[/]: {info['download_url']}") -@client.command(name="set", context_settings=dict(help_option_names=["-h", "--help"])) -@click.argument("client_name", required=True) -def set_client(client_name): - """Set the active MCP client. - - CLIENT is the name of the client to set as active. - """ - - # Get the list of supported clients - supported_clients = ClientRegistry.get_supported_clients() - - # Set the active client if provided - if client_name not in supported_clients: - console.print(f"[bold red]Error:[/] Unknown client: {client_name}") - console.print(f"Supported clients: {', '.join(sorted(supported_clients))}") - return - - # Set the active client - if client_name == ClientRegistry.get_active_client(): - console.print(f"[bold yellow]Note:[/] {client_name} is already the active client") - return - - # 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( - f"The active client ({client_name}) will be used for all MCP operations.\n" - f"Commands like 'mcpm ls', 'mcpm add', 'mcpm rm', 'mcpm stash', and 'mcpm pop' will now operate on {client_name}.", - title="Active Client Changed", - border_style="green", - ) - console.print(panel) - else: - console.print(f"[bold red]Error:[/] Failed to set {client_name} as the active client") - - @client.command(name="edit", context_settings=dict(help_option_names=["-h", "--help"])) def edit_client(): """Open the active client's MCP settings in external editor.""" # Get the active client manager and related information client_manager = ClientRegistry.get_active_client_manager() - client = ClientRegistry.get_active_client() - client_info = ClientRegistry.get_client_info(client) - client_name = client_info.get("name", client) - # Check if client is supported if client_manager is None: - print_client_error(client_name) + print_client_error() return + client = ClientRegistry.get_active_client() + client_info = ClientRegistry.get_client_info(client) + client_name = client_info.get("name", client) # Get the client config file path config_path = client_manager.config_path diff --git a/src/mcpm/commands/list.py b/src/mcpm/commands/list.py index 21ed6f8..35012ec 100644 --- a/src/mcpm/commands/list.py +++ b/src/mcpm/commands/list.py @@ -8,7 +8,7 @@ from mcpm.clients.client_config import ClientConfigManager from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.common import determine_scope +from mcpm.commands.target_operations.common import determine_scope from mcpm.core.schema import ServerConfig from mcpm.profile.profile_config import ProfileConfigManager from mcpm.utils.display import print_client_error, print_server_config diff --git a/src/mcpm/commands/profile.py b/src/mcpm/commands/profile.py index 555d374..5f06e2e 100644 --- a/src/mcpm/commands/profile.py +++ b/src/mcpm/commands/profile.py @@ -18,104 +18,6 @@ def profile(): pass -@click.command() -@click.argument("profile_name") -@click.option("--client", "-c", help="Client of the profile") -@click.help_option("-h", "--help") -def activate(profile_name, client=None): - """Activate a profile. - - Sets the specified profile as the active profile. - """ - # Activate the specified profile - if profile_config_manager.get_profile(profile_name) is None: - console.print(f"[bold red]Error:[/] Profile '{profile_name}' not found.") - return - - # Set the active profile - 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: - console.print(f"[bold red]Error:[/] Client '{client}' not found.") - return - success = client_manager.activate_profile(profile_name, config_manager.get_router_config()) - else: - client = ClientRegistry.get_active_client() - if client is None: - console.print("[bold yellow]No active client found.[/]\n") - return - console.print(f"[bold cyan]Activating profile '{profile_name}' in active client '{client}'...[/]") - client_manager = ClientRegistry.get_client_manager(client) - if client_manager is 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 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}'.") - - -@click.command() -@click.option("--client", "-c", help="Client of the profile") -@click.help_option("-h", "--help") -def deactivate(client=None): - """Deactivate a profile. - - Unsets the active profile. - """ - deactivate_this_client: bool = client is None - - # Set the active profile - active_profile = ClientRegistry.get_active_profile() - 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: - console.print(f"[bold red]Error:[/] Client '{client}' not found.") - return - success = client_manager.deactivate_profile() - else: - client = ClientRegistry.get_active_client() - if client is None: - console.print("[bold yellow]No active client found.[/]\n") - return - console.print(f"[bold cyan]Deactivating profile '{active_profile}' in active client '{client}'...[/]") - client_manager = ClientRegistry.get_client_manager(client) - if client_manager is None: - console.print(f"[bold red]Error:[/] Client '{client}' not found.") - return - success = client_manager.deactivate_profile() - 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}'.") - - @profile.command(name="ls") @click.option("--verbose", "-v", is_flag=True, help="Show detailed server information") @click.help_option("-h", "--help") @@ -166,43 +68,6 @@ def add(profile, force=False): ) -@profile.command() -@click.argument("profile") -@click.option("--server", "-s", required=True, help="Server to apply config to") -@click.help_option("-h", "--help") -def apply(profile, server): - """Apply an existing MCPM config to a profile.""" - client_manager = ClientRegistry.get_active_client_manager() - client = ClientRegistry.get_active_client() - if client is None: - console.print("[bold red]Error:[/] No active client found.") - return - client_info = ClientRegistry.get_client_info(client) - client_name = client_info.get("name", client) - - # Check if client is supported - if client_manager is None: - console.print("[bold red]Error:[/] Unsupported active client") - console.print("Please switch to a supported client using 'mcpm client set '") - return - - # Check if the server exists in the active client - server_info = client_manager.get_server(server) - if server_info is None: - console.print(f"[bold red]Error:[/] Server '{server}' not found in {client_name}.") - return - - # Get profile - profile_info = profile_config_manager.get_profile(profile) - if profile_info is None: - console.print(f"[bold red]Error:[/] Profile '{profile}' not found.") - return - - # Save profile - profile_config_manager.set_profile(profile, server_info) - console.print(f"\n[green]Server '{server}' applied to profile '{profile}' successfully.[/]\n") - - @profile.command("rm") @click.argument("profile_name") @click.help_option("-h", "--help") @@ -220,12 +85,12 @@ def remove(profile_name): 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") + console.print(f"\n[green]Profile '{profile_name}' removed successfully from client '{client}'.[/]\n") # fresh the active_profile activated_profile = ClientRegistry.get_active_profile() if activated_profile == profile_name: - ClientRegistry.set_active_profile(None) + ClientRegistry.set_active_target(None) console.print(f"\n[green]Profile '{profile_name}' deleted successfully.[/]\n") @@ -253,11 +118,11 @@ def rename(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") + console.print(f"\n[green]Profile '{profile_name}' replaced successfully in 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) + ClientRegistry.set_active_target(new_profile_name) console.print(f"\n[green]Profile '{profile_name}' renamed to '{new_profile_name}' successfully.[/]\n") diff --git a/src/mcpm/commands/target.py b/src/mcpm/commands/target.py new file mode 100644 index 0000000..3e144a6 --- /dev/null +++ b/src/mcpm/commands/target.py @@ -0,0 +1,84 @@ +import click +from rich.console import Console +from rich.panel import Panel + +from mcpm.clients.client_registry import ClientRegistry +from mcpm.profile.profile_config import ProfileConfigManager +from mcpm.utils.scope import ScopeType, extract_from_scope, format_scope + +console = Console() + + +@click.group() +@click.help_option("-h", "--help") +def target(): + """Manage MCPM working target.""" + pass + + +@target.command(name="set", context_settings=dict(help_option_names=["-h", "--help"])) +@click.argument("target", required=True) +def set_target(target): + """Set the active MCPM working target. + + TARGET is the name of the client or profile to set as active. + Examples: + + \b + mcpm target set @windsurf + mcpm target set %profile_dev + """ + + scope_type, scope = extract_from_scope(target) + if not scope: + console.print(f"[bold red]Error:[/] Invalid target: {target}") + return + scope_name = format_scope(scope_type, scope) + # Set the active target + if scope_name == ClientRegistry.get_active_target(): + console.print(f"[bold yellow]Note:[/] {target} is already the active target") + return + + success = False + if scope_type == ScopeType.CLIENT: + # Get the list of supported clients + supported_clients = ClientRegistry.get_supported_clients() + + # Set the active client if provided + if scope not in supported_clients: + console.print(f"[bold red]Error:[/] Unknown client: {scope}") + console.print(f"Supported clients: {', '.join(sorted(supported_clients))}") + return + + # Attempt to set the active client with active profile inner switched + success = ClientRegistry.set_active_target(scope_name) + if success: + console.print(f"[bold green]Success:[/] Active client set to {scope}") + else: + # Set the active profile + active_profile = ClientRegistry.get_active_target() + if active_profile: + console.print(f"[bold green]Success:[/] Active profile set to {active_profile}") + + profiles = ProfileConfigManager().list_profiles() + if scope not in profiles: + console.print(f"[bold red]Error:[/] Unknown profile: {scope}") + console.print(f"Available profiles: {', '.join(sorted(profiles.keys()))}") + return + + # Attempt to set the active profile with active client inner switched + success = ClientRegistry.set_active_target(scope_name) + if success: + console.print(f"[bold green]Success:[/] Active profile set to {scope}") + + if success: + # Provide information about what this means + panel = Panel( + f"The active target ({scope_name}) will be used for all MCP operations.\n" + f"Commands like 'mcpm ls', 'mcpm add', 'mcpm rm', 'mcpm stash', and 'mcpm pop' will now operate on {scope_name}.", + title="Active Target Changed", + border_style="green", + ) + console.print(panel) + else: + console.print(f"[bold red]Error:[/] Failed to set {target} as the active target") diff --git a/src/mcpm/commands/server_operations/add.py b/src/mcpm/commands/target_operations/add.py similarity index 94% rename from src/mcpm/commands/server_operations/add.py rename to src/mcpm/commands/target_operations/add.py index 0fce3f4..fc089da 100644 --- a/src/mcpm/commands/server_operations/add.py +++ b/src/mcpm/commands/target_operations/add.py @@ -17,11 +17,16 @@ from rich.prompt import Confirm from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.common import client_add_server, determine_scope, profile_add_server +from mcpm.commands.target_operations.common import ( + client_add_profile, + client_add_server, + determine_scope, + profile_add_server, +) from mcpm.profile.profile_config import ProfileConfigManager from mcpm.schemas.full_server_config import FullServerConfig from mcpm.utils.repository import RepositoryManager -from mcpm.utils.scope import ScopeType +from mcpm.utils.scope import PROFILE_PREFIX, ScopeType console = Console() repo_manager = RepositoryManager() @@ -102,14 +107,20 @@ def add(server_name, force=False, alias=None, target: str | None = None): mcpm add everything --force mcpm add youtube --alias yt mcpm add youtube --target %myprofile + mcpm add %profile --target @windsurf """ config_name = alias or server_name + is_adding_profile = server_name.startswith(PROFILE_PREFIX) scope_type, scope = determine_scope(target) if not scope: return if scope_type == ScopeType.PROFILE: + if is_adding_profile: + console.print("[bold red]Error:[/] Cannot add profile to profile.") + return + # Get profile profile = scope console.print(f"[yellow]Adding server to profile: {profile}[/]") @@ -131,8 +142,11 @@ def add(server_name, force=False, alias=None, target: str | None = None): target_name = profile else: - # Get client client = scope + if is_adding_profile: + add_profile_to_client(server_name, client, alias, force) + return + # Get client console.print(f"[yellow]Adding server to client: {client}[/]") client_info = ClientRegistry.get_client_info(client) if client_info is None: @@ -506,3 +520,15 @@ def _replace_argument_variables(value: str, prev_value: str, variables: dict) -> # nothing to replace return value, ReplacementStatus.NOT_REPLACED + + +def add_profile_to_client(profile_name: str, client: str, alias: str | None = None, force: bool = False): + if not force and not Confirm.ask(f"Add this profile to {client}{' as ' + alias if alias else ''}?"): + console.print("[yellow]Operation cancelled.[/]") + return + + success = client_add_profile(profile_name, client) + if success: + console.print(f"[bold green]Successfully added profile {profile_name} to {client}![/]") + else: + console.print(f"[bold red]Failed to add profile {profile_name} to {client}.[/]") diff --git a/src/mcpm/commands/server_operations/common.py b/src/mcpm/commands/target_operations/common.py similarity index 85% rename from src/mcpm/commands/server_operations/common.py rename to src/mcpm/commands/target_operations/common.py index 03f86d8..da0d124 100644 --- a/src/mcpm/commands/server_operations/common.py +++ b/src/mcpm/commands/target_operations/common.py @@ -3,6 +3,7 @@ from mcpm.clients.client_registry import ClientRegistry from mcpm.core.schema import ServerConfig from mcpm.profile.profile_config import ProfileConfigManager +from mcpm.utils.config import ConfigManager from mcpm.utils.display import print_active_scope, print_no_active_scope from mcpm.utils.scope import ScopeType, extract_from_scope, parse_server @@ -12,7 +13,7 @@ def determine_scope(scope: str | None) -> tuple[ScopeType | None, str | None]: if not scope: # Get the active scope - scope = ClientRegistry.determine_active_scope() + scope = ClientRegistry.get_active_target() if not scope: print_no_active_scope() return None, None @@ -91,3 +92,17 @@ def profile_get_server(profile: str, server: str) -> ServerConfig | None: console.print(f"[bold red]Error:[/] Profile '{profile}' not found.") return None return profile_manager.get_profile_server(profile, server) + + +def client_add_profile(profile_name: str, client: str) -> bool: + client_manager = ClientRegistry.get_client_manager(client) + if not client_manager: + console.print(f"[bold red]Error:[/] Client '{client}' not found.") + return False + router_config = ConfigManager().get_router_config() + if not router_config: + console.print("[bold red]Error:[/] Router config not found.") + return False + + success = client_manager.activate_profile(profile_name, router_config) + return success diff --git a/src/mcpm/commands/server_operations/custom.py b/src/mcpm/commands/target_operations/custom.py similarity index 99% rename from src/mcpm/commands/server_operations/custom.py rename to src/mcpm/commands/target_operations/custom.py index e379716..771d66a 100644 --- a/src/mcpm/commands/server_operations/custom.py +++ b/src/mcpm/commands/target_operations/custom.py @@ -4,7 +4,7 @@ from rich.console import Console from rich.prompt import Confirm, Prompt -from mcpm.commands.server_operations.common import client_add_server, determine_scope, profile_add_server +from mcpm.commands.target_operations.common import client_add_server, determine_scope, profile_add_server from mcpm.core.schema import SSEServerConfig, STDIOServerConfig from mcpm.utils.display import print_server_config from mcpm.utils.scope import ScopeType diff --git a/src/mcpm/commands/server_operations/pop.py b/src/mcpm/commands/target_operations/pop.py similarity index 98% rename from src/mcpm/commands/server_operations/pop.py rename to src/mcpm/commands/target_operations/pop.py index 35feed8..f01d923 100644 --- a/src/mcpm/commands/server_operations/pop.py +++ b/src/mcpm/commands/target_operations/pop.py @@ -8,7 +8,7 @@ from mcpm.clients.client_config import ClientConfigManager from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.common import determine_target +from mcpm.commands.target_operations.common import determine_target from mcpm.core.schema import ServerConfig from mcpm.profile.profile_config import ProfileConfigManager from mcpm.utils.scope import ScopeType, format_scope diff --git a/src/mcpm/commands/server_operations/remove.py b/src/mcpm/commands/target_operations/remove.py similarity index 94% rename from src/mcpm/commands/server_operations/remove.py rename to src/mcpm/commands/target_operations/remove.py index 832b1f3..1cb5071 100644 --- a/src/mcpm/commands/server_operations/remove.py +++ b/src/mcpm/commands/target_operations/remove.py @@ -6,7 +6,7 @@ from rich.console import Console from rich.prompt import Confirm -from mcpm.commands.server_operations.common import ( +from mcpm.commands.target_operations.common import ( client_get_server, client_remove_server, determine_target, @@ -21,7 +21,7 @@ @click.command() @click.argument("server_name") -@click.option("--force", is_flag=True, help="Force removal without confirmation") +@click.option("--force", "-f", is_flag=True, help="Force removal without confirmation") @click.help_option("-h", "--help") def remove(server_name, force): """Remove an installed MCP server. diff --git a/src/mcpm/commands/server_operations/stash.py b/src/mcpm/commands/target_operations/stash.py similarity index 98% rename from src/mcpm/commands/server_operations/stash.py rename to src/mcpm/commands/target_operations/stash.py index a133a83..f4ca233 100644 --- a/src/mcpm/commands/server_operations/stash.py +++ b/src/mcpm/commands/target_operations/stash.py @@ -6,7 +6,7 @@ from rich.console import Console from mcpm.clients.client_config import ClientConfigManager -from mcpm.commands.server_operations.common import ( +from mcpm.commands.target_operations.common import ( client_get_server, client_remove_server, determine_target, diff --git a/src/mcpm/commands/server_operations/transfer.py b/src/mcpm/commands/target_operations/transfer.py similarity index 98% rename from src/mcpm/commands/server_operations/transfer.py rename to src/mcpm/commands/target_operations/transfer.py index 8a5b510..696fdd6 100644 --- a/src/mcpm/commands/server_operations/transfer.py +++ b/src/mcpm/commands/target_operations/transfer.py @@ -2,7 +2,7 @@ from rich.console import Console from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.common import ( +from mcpm.commands.target_operations.common import ( client_add_server, client_get_server, client_remove_server, @@ -25,7 +25,7 @@ def determine_source_and_destination( source_context_type, source_context, source_server = determine_target(source) destination_context_type, destination_context, destination_server = determine_target(destination) if not source_context or not destination_context: - active_context = ClientRegistry.determine_active_scope() + active_context = ClientRegistry.get_active_target() if not active_context: print_no_active_scope() return None, None, None, None, None, None diff --git a/src/mcpm/utils/display.py b/src/mcpm/utils/display.py index af3bede..549e893 100644 --- a/src/mcpm/utils/display.py +++ b/src/mcpm/utils/display.py @@ -130,6 +130,4 @@ def print_active_scope(scope: str): def print_no_active_scope(): console.print("[bold red]Error:[/] No active client or profile found.\n") - console.print( - "Please set an active client with 'mcpm client set ' or a profile with 'mcpm activate '." - ) + console.print("Please set an active target with 'mcpm target set @' or 'mcpm target set %'.") diff --git a/tests/test_add.py b/tests/test_add.py index 77c4b94..6e46544 100644 --- a/tests/test_add.py +++ b/tests/test_add.py @@ -3,8 +3,9 @@ from click.testing import CliRunner from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.add import add +from mcpm.commands.target_operations.add import add from mcpm.core.schema import SSEServerConfig +from mcpm.utils.config import ConfigManager from mcpm.utils.repository import RepositoryManager @@ -12,7 +13,7 @@ def test_add_server(windsurf_manager, monkeypatch): """Test add server""" monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr( RepositoryManager, "_fetch_servers", @@ -54,7 +55,7 @@ def test_add_server_with_missing_arg(windsurf_manager, monkeypatch): """Test add server with a missing argument that should be replaced with empty string""" monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr( RepositoryManager, "_fetch_servers", @@ -119,7 +120,7 @@ def test_add_server_with_empty_args(windsurf_manager, monkeypatch): monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr( RepositoryManager, "_fetch_servers", @@ -206,3 +207,19 @@ def test_add_sse_server_to_claude_desktop(claude_desktop_manager, monkeypatch): "Authorization", "Bearer test-api-key", ] + + +def test_add_profile_to_client(windsurf_manager, monkeypatch): + profile_name = "work" + client_name = "windsurf" + + monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) + monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value=client_name)) + monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) + monkeypatch.setattr(ConfigManager, "get_router_config", Mock(return_value={"host": "localhost", "port": 8080})) + + # test cli + runner = CliRunner() + result = runner.invoke(add, ["%" + profile_name, "--force", "--alias", "work"]) + assert result.exit_code == 0 + assert "Successfully added profile: work" in result.output diff --git a/tests/test_client.py b/tests/test_client.py index b8c32a4..d7467d5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,7 +8,7 @@ from click.testing import CliRunner from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.client import client, edit_client, list_clients, set_client +from mcpm.commands.client import client, edit_client, list_clients def test_client_ls_command(monkeypatch): @@ -45,95 +45,95 @@ def mock_get_client_info(client_name): assert "Not installed" in result.output -def test_client_set_command_success(monkeypatch): - """Test successful 'client set' command""" - # Mock supported clients - supported_clients = ["claude-desktop", "windsurf", "cursor"] - monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) +# def test_client_set_command_success(monkeypatch): +# """Test successful 'client set' command""" +# # Mock supported clients +# supported_clients = ["claude-desktop", "windsurf", "cursor"] +# monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) - # Mock active client different from what we're setting - monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="claude-desktop")) +# # Mock active client different from what we're setting +# monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="claude-desktop")) - # Mock set_active_client to succeed - mock_set_active_client = Mock(return_value=True) - monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) +# # Mock set_active_client to succeed +# mock_set_active_client = Mock(return_value=True) +# monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) - # Run the command - runner = CliRunner() - result = runner.invoke(set_client, ["windsurf"]) +# # Run the command +# runner = CliRunner() +# result = runner.invoke(set_client, ["windsurf"]) - # Check the result - assert result.exit_code == 0 - assert "Success" in result.output - assert "Active client set to windsurf" in result.output - mock_set_active_client.assert_called_once_with("windsurf") +# # Check the result +# assert result.exit_code == 0 +# assert "Success" in result.output +# assert "Active client set to windsurf" in result.output +# mock_set_active_client.assert_called_once_with("windsurf") -def test_client_set_command_already_active(monkeypatch): - """Test 'client set' when client is already active""" - # Mock supported clients - supported_clients = ["claude-desktop", "windsurf", "cursor"] - monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) +# def test_client_set_command_already_active(monkeypatch): +# """Test 'client set' when client is already active""" +# # Mock supported clients +# supported_clients = ["claude-desktop", "windsurf", "cursor"] +# monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) - # Mock active client same as what we're setting - monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="windsurf")) +# # Mock active client same as what we're setting +# monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="windsurf")) - # Mock set_active_client - mock_set_active_client = Mock(return_value=True) - monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) +# # Mock set_active_client +# mock_set_active_client = Mock(return_value=True) +# monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) - # Run the command - runner = CliRunner() - result = runner.invoke(set_client, ["windsurf"]) +# # Run the command +# runner = CliRunner() +# result = runner.invoke(set_client, ["windsurf"]) - # Check the result - assert result.exit_code == 0 - assert "windsurf is already the active client" in result.output - # set_active_client should not be called - mock_set_active_client.assert_not_called() +# # Check the result +# assert result.exit_code == 0 +# assert "windsurf is already the active client" in result.output +# # set_active_client should not be called +# mock_set_active_client.assert_not_called() -def test_client_set_command_unsupported(monkeypatch): - """Test 'client set' with unsupported client""" - # Mock supported clients - supported_clients = ["claude-desktop", "windsurf", "cursor"] - monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) +# def test_client_set_command_unsupported(monkeypatch): +# """Test 'client set' with unsupported client""" +# # Mock supported clients +# supported_clients = ["claude-desktop", "windsurf", "cursor"] +# monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) - # Run the command with unsupported client - runner = CliRunner() - result = runner.invoke(set_client, ["unsupported-client"]) +# # Run the command with unsupported client +# runner = CliRunner() +# result = runner.invoke(set_client, ["unsupported-client"]) - # Check the result - assert result.exit_code == 0 - assert "Error" in result.output - assert "Unknown client: unsupported-client" in result.output - # Verify supported clients are listed - for supported_client in supported_clients: - assert supported_client in result.output +# # Check the result +# assert result.exit_code == 0 +# assert "Error" in result.output +# assert "Unknown client: unsupported-client" in result.output +# # Verify supported clients are listed +# for supported_client in supported_clients: +# assert supported_client in result.output -def test_client_set_command_failure(monkeypatch): - """Test 'client set' when setting fails""" - # Mock supported clients - supported_clients = ["claude-desktop", "windsurf", "cursor"] - monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) +# def test_client_set_command_failure(monkeypatch): +# """Test 'client set' when setting fails""" +# # Mock supported clients +# supported_clients = ["claude-desktop", "windsurf", "cursor"] +# monkeypatch.setattr(ClientRegistry, "get_supported_clients", Mock(return_value=supported_clients)) - # Mock active client different from what we're setting - monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="claude-desktop")) +# # Mock active client different from what we're setting +# monkeypatch.setattr(ClientRegistry, "get_active_client", Mock(return_value="claude-desktop")) - # Mock set_active_client to fail - mock_set_active_client = Mock(return_value=False) - monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) +# # Mock set_active_client to fail +# mock_set_active_client = Mock(return_value=False) +# monkeypatch.setattr(ClientRegistry, "set_active_client", mock_set_active_client) - # Run the command - runner = CliRunner() - result = runner.invoke(set_client, ["windsurf"]) +# # Run the command +# runner = CliRunner() +# result = runner.invoke(set_client, ["windsurf"]) - # Check the result - assert result.exit_code == 0 - assert "Error" in result.output - assert "Failed to set windsurf as the active client" in result.output - mock_set_active_client.assert_called_once_with("windsurf") +# # Check the result +# assert result.exit_code == 0 +# assert "Error" in result.output +# assert "Failed to set windsurf as the active client" in result.output +# mock_set_active_client.assert_called_once_with("windsurf") def test_client_edit_command_client_not_supported(monkeypatch): diff --git a/tests/test_remove.py b/tests/test_remove.py index 151e281..7260483 100644 --- a/tests/test_remove.py +++ b/tests/test_remove.py @@ -3,7 +3,7 @@ from click.testing import CliRunner from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.remove import remove +from mcpm.commands.target_operations.remove import remove def test_remove_server_success(windsurf_manager, monkeypatch): @@ -12,7 +12,7 @@ def test_remove_server_success(windsurf_manager, monkeypatch): monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) # Mock server info mock_server = Mock() @@ -36,7 +36,7 @@ def test_remove_server_not_found(windsurf_manager, monkeypatch): monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) # Mock server not found windsurf_manager.get_server = Mock(return_value=None) @@ -51,7 +51,7 @@ def test_remove_server_not_found(windsurf_manager, monkeypatch): def test_remove_server_unsupported_client(monkeypatch): """Test removal with unsupported client""" monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=None)) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@unsupported")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@unsupported")) runner = CliRunner() result = runner.invoke(remove, ["server-test"]) @@ -65,7 +65,7 @@ def test_remove_server_cancelled(windsurf_manager, monkeypatch): monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) # Mock server info mock_server = Mock() @@ -90,7 +90,7 @@ def test_remove_server_failure(windsurf_manager, monkeypatch): monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) # Mock server info mock_server = Mock() diff --git a/tests/test_stash_pop.py b/tests/test_stash_pop.py index b396b46..0da5ebe 100644 --- a/tests/test_stash_pop.py +++ b/tests/test_stash_pop.py @@ -3,14 +3,14 @@ from click.testing import CliRunner from mcpm.clients.client_registry import ClientRegistry -from mcpm.commands.server_operations.pop import pop -from mcpm.commands.server_operations.stash import stash +from mcpm.commands.target_operations.pop import pop +from mcpm.commands.target_operations.stash import stash def test_stash_server_success(windsurf_manager, monkeypatch): """Test successful server stashing""" # Setup mocks - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) @@ -49,7 +49,7 @@ def test_stash_server_success(windsurf_manager, monkeypatch): def test_stash_server_already_stashed(windsurf_manager, monkeypatch): """Test stashing an already stashed server""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -74,7 +74,7 @@ def test_stash_server_already_stashed(windsurf_manager, monkeypatch): def test_stash_server_remove_failure(windsurf_manager, monkeypatch): """Test stashing when server removal fails""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -103,7 +103,7 @@ def test_stash_server_remove_failure(windsurf_manager, monkeypatch): def test_stash_server_not_found(windsurf_manager, monkeypatch): """Test stashing a non-existent server""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -126,7 +126,7 @@ def test_stash_server_not_found(windsurf_manager, monkeypatch): def test_stash_server_unsupported_client(monkeypatch): """Test stashing with unsupported client""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@unsupported")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@unsupported")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=None)) # Mock client config manager @@ -143,7 +143,7 @@ def test_stash_server_unsupported_client(monkeypatch): def test_pop_server_success(windsurf_manager, monkeypatch): """Test successful server restoration""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -178,7 +178,7 @@ def test_pop_server_success(windsurf_manager, monkeypatch): def test_pop_server_not_stashed(windsurf_manager, monkeypatch): """Test popping a non-stashed server""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -198,7 +198,7 @@ def test_pop_server_not_stashed(windsurf_manager, monkeypatch): def test_pop_server_add_failure(windsurf_manager, monkeypatch): """Test popping when server addition fails""" - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@windsurf")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@windsurf")) monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=windsurf_manager)) monkeypatch.setattr(ClientRegistry, "get_client_info", Mock(return_value={"name": "windsurf"})) monkeypatch.setattr(ClientRegistry, "get_client_manager", Mock(return_value=windsurf_manager)) @@ -233,7 +233,7 @@ def test_pop_server_add_failure(windsurf_manager, monkeypatch): def test_pop_server_unsupported_client(monkeypatch): """Test popping with unsupported client""" monkeypatch.setattr(ClientRegistry, "get_active_client_manager", Mock(return_value=None)) - monkeypatch.setattr(ClientRegistry, "determine_active_scope", Mock(return_value="@unsupported")) + monkeypatch.setattr(ClientRegistry, "get_active_target", Mock(return_value="@unsupported")) # Mock client config manager mock_config_manager = Mock() diff --git a/tests/test_windsurf.py b/tests/test_windsurf.py index 0ce3c10..9178712 100644 --- a/tests/test_windsurf.py +++ b/tests/test_windsurf.py @@ -213,8 +213,9 @@ def test_distributed_architecture(self, config_manager, windsurf_manager, sample assert "windsurf" in supported_clients # Test setting Windsurf as active client - success = config_manager.set_active_client("windsurf") + success = config_manager.set_active_target("windsurf") assert success + assert config_manager.get_active_target() == "windsurf" assert config_manager.get_active_client() == "windsurf" # In the distributed architecture, each client manages its own servers