|
2 | 2 | Client command for MCPM |
3 | 3 | """ |
4 | 4 |
|
| 5 | +import json |
| 6 | +import os |
| 7 | +import subprocess |
| 8 | + |
5 | 9 | import click |
6 | 10 | from rich.console import Console |
7 | 11 | from rich.panel import Panel |
| 12 | +from rich.prompt import Confirm |
8 | 13 | from rich.table import Table |
9 | 14 |
|
10 | 15 | from mcpm.clients.client_config import ClientConfigManager |
11 | 16 | from mcpm.clients.client_registry import ClientRegistry |
| 17 | +from mcpm.utils.display import print_client_error, print_error, print_server_config |
12 | 18 |
|
13 | 19 | console = Console() |
14 | 20 | client_config_manager = ClientConfigManager() |
15 | 21 |
|
16 | 22 |
|
17 | | -@click.command() |
18 | | -@click.argument("client_name", required=False) |
19 | | -@click.option("--list", is_flag=True, help="List all supported clients") |
20 | | -def client(client_name, list): |
21 | | - """Manage the active MCP client. |
| 23 | +@click.group(context_settings=dict(help_option_names=["-h", "--help"])) |
| 24 | +def client(): |
| 25 | + """Manage MCP clients. |
22 | 26 |
|
23 | | - If no arguments are provided, shows the current active client. |
24 | | - If a client name is provided, sets it as the active client. |
| 27 | + Commands for listing, setting the active client, and editing client configurations. |
25 | 28 |
|
26 | 29 | Examples: |
27 | | - mcpm client # Show current active client |
28 | | - mcpm client --list # List all supported clients |
29 | | - mcpm client claude-desktop # Set Claude Desktop as the active client |
| 30 | +
|
| 31 | + \b |
| 32 | + mcpm client ls # List all supported MCP clients and their status |
| 33 | + mcpm client set CLIENT # Set the active client |
| 34 | + mcpm client edit # Open active client MCP settings in external editor |
30 | 35 | """ |
| 36 | + pass |
| 37 | + |
| 38 | + |
| 39 | +@client.command(name="ls", context_settings=dict(help_option_names=["-h", "--help"])) |
| 40 | +def list_clients(): |
| 41 | + """List all supported MCP clients and their status.""" |
31 | 42 | # Get the list of supported clients |
32 | 43 | supported_clients = ClientRegistry.get_supported_clients() |
33 | 44 |
|
34 | | - # List all supported clients if requested |
35 | | - if list: |
36 | | - table = Table(title="Supported MCP Clients") |
37 | | - table.add_column("Client Name", style="cyan") |
38 | | - table.add_column("Installation", style="yellow") |
39 | | - table.add_column("Status", style="green") |
| 45 | + table = Table(title="Supported MCP Clients") |
| 46 | + table.add_column("Client Name", style="cyan") |
| 47 | + table.add_column("Installation", style="yellow") |
| 48 | + table.add_column("Status", style="green") |
40 | 49 |
|
41 | | - active_client = ClientRegistry.get_active_client() |
42 | | - installed_clients = ClientRegistry.detect_installed_clients() |
| 50 | + active_client = ClientRegistry.get_active_client() |
| 51 | + installed_clients = ClientRegistry.detect_installed_clients() |
43 | 52 |
|
44 | | - for client in sorted(supported_clients): |
45 | | - # Determine installation status |
46 | | - installed = installed_clients.get(client, False) |
47 | | - install_status = "[green]Installed[/]" if installed else "[gray]Not installed[/]" |
| 53 | + for client in sorted(supported_clients): |
| 54 | + # Determine installation status |
| 55 | + installed = installed_clients.get(client, False) |
| 56 | + install_status = "[green]Installed[/]" if installed else "[gray]Not installed[/]" |
48 | 57 |
|
49 | | - # Determine active status |
50 | | - active_status = "[bold green]ACTIVE[/]" if client == active_client else "" |
| 58 | + # Determine active status |
| 59 | + active_status = "[bold green]ACTIVE[/]" if client == active_client else "" |
51 | 60 |
|
52 | | - # Get client info for more details |
53 | | - client_info = ClientRegistry.get_client_info(client) |
54 | | - display_name = client_info.get("name", client) |
| 61 | + # Get client info for more details |
| 62 | + client_info = ClientRegistry.get_client_info(client) |
| 63 | + display_name = client_info.get("name", client) |
55 | 64 |
|
56 | | - table.add_row(f"{display_name} ({client})", install_status, active_status) |
| 65 | + table.add_row(f"{display_name} ({client})", install_status, active_status) |
57 | 66 |
|
58 | | - console.print(table) |
| 67 | + console.print(table) |
59 | 68 |
|
60 | | - # Add helpful instructions for non-installed clients |
61 | | - non_installed = [c for c, installed in installed_clients.items() if not installed] |
62 | | - if non_installed: |
63 | | - console.print("\n[italic]To use a non-installed client, you need to install it first.[/]") |
64 | | - for client in non_installed: |
65 | | - info = ClientRegistry.get_client_info(client) |
66 | | - if "download_url" in info: |
67 | | - console.print(f"[yellow]{info.get('name', client)}[/]: {info['download_url']}") |
| 69 | + # Add helpful instructions for non-installed clients |
| 70 | + non_installed = [c for c, installed in installed_clients.items() if not installed] |
| 71 | + if non_installed: |
| 72 | + console.print("\n[italic]To use a non-installed client, you need to install it first.[/]") |
| 73 | + for client in non_installed: |
| 74 | + info = ClientRegistry.get_client_info(client) |
| 75 | + if "download_url" in info: |
| 76 | + console.print(f"[yellow]{info.get('name', client)}[/]: {info['download_url']}") |
68 | 77 |
|
69 | | - return |
70 | 78 |
|
71 | | - # If no client name specified, show the current active client |
72 | | - if not client_name: |
73 | | - active_client = ClientRegistry.get_active_client() |
74 | | - console.print(f"Current active client: [bold cyan]{active_client}[/]") |
| 79 | +@client.command(name="set", context_settings=dict(help_option_names=["-h", "--help"])) |
| 80 | +@click.argument("client_name", required=True) |
| 81 | +def set_client(client_name): |
| 82 | + """Set the active MCP client. |
75 | 83 |
|
76 | | - # Display some helpful information about setting clients |
77 | | - console.print("\nTo change the active client, run:") |
78 | | - for client in sorted(supported_clients): |
79 | | - if client != active_client: |
80 | | - console.print(f" mcpm client {client}") |
81 | | - |
82 | | - # Display a note about using --list |
83 | | - console.print("\nTo see all supported clients:") |
84 | | - console.print(" mcpm client --list") |
85 | | - return |
| 84 | + CLIENT is the name of the client to set as active. |
| 85 | + """ |
| 86 | + # Get the list of supported clients |
| 87 | + supported_clients = ClientRegistry.get_supported_clients() |
86 | 88 |
|
87 | 89 | # Set the active client if provided |
88 | 90 | if client_name not in supported_clients: |
@@ -110,3 +112,115 @@ def client(client_name, list): |
110 | 112 | console.print(panel) |
111 | 113 | else: |
112 | 114 | console.print(f"[bold red]Error:[/] Failed to set {client_name} as the active client") |
| 115 | + |
| 116 | + |
| 117 | +@client.command(name="edit", context_settings=dict(help_option_names=["-h", "--help"])) |
| 118 | +def edit_client(): |
| 119 | + """Open the active client's MCP settings in external editor.""" |
| 120 | + # Get the active client manager and related information |
| 121 | + client_manager = ClientRegistry.get_active_client_manager() |
| 122 | + client = ClientRegistry.get_active_client() |
| 123 | + client_info = ClientRegistry.get_client_info(client) |
| 124 | + client_name = client_info.get("name", client) |
| 125 | + |
| 126 | + # Check if client is supported |
| 127 | + if client_manager is None: |
| 128 | + print_client_error(client_name) |
| 129 | + return |
| 130 | + |
| 131 | + # Get the client config file path |
| 132 | + config_path = client_manager.config_path |
| 133 | + |
| 134 | + # Check if the client is installed |
| 135 | + if not client_manager.is_client_installed(): |
| 136 | + print_error(f"{client_name} installation not detected.") |
| 137 | + return |
| 138 | + |
| 139 | + # Check if config file exists |
| 140 | + config_exists = os.path.exists(config_path) |
| 141 | + |
| 142 | + # Display the config file information |
| 143 | + console.print(f"[bold]{client_name} config file:[/] {config_path}") |
| 144 | + console.print(f"[bold]Status:[/] {'[green]Exists[/]' if config_exists else '[yellow]Does not exist[/]'}\n") |
| 145 | + |
| 146 | + # Create config file if it doesn't exist and user confirms |
| 147 | + if not config_exists: |
| 148 | + console.print(f"[bold yellow]Creating new {client_name} config file...[/]") |
| 149 | + |
| 150 | + # Create a basic config template |
| 151 | + basic_config = { |
| 152 | + "mcpServers": { |
| 153 | + "filesystem": { |
| 154 | + "command": "npx", |
| 155 | + "args": [ |
| 156 | + "-y", |
| 157 | + "@modelcontextprotocol/server-filesystem", |
| 158 | + os.path.expanduser("~/Desktop"), |
| 159 | + os.path.expanduser("~/Downloads"), |
| 160 | + ], |
| 161 | + } |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + # Create the directory if it doesn't exist |
| 166 | + os.makedirs(os.path.dirname(config_path), exist_ok=True) |
| 167 | + |
| 168 | + # Write the template to file |
| 169 | + try: |
| 170 | + with open(config_path, "w") as f: |
| 171 | + json.dump(basic_config, f, indent=2) |
| 172 | + console.print("[green]Successfully created config file![/]\n") |
| 173 | + config_exists = True |
| 174 | + except Exception as e: |
| 175 | + print_error("Error creating config file", str(e)) |
| 176 | + return |
| 177 | + |
| 178 | + # Show the current configuration if it exists |
| 179 | + if config_exists: |
| 180 | + try: |
| 181 | + with open(config_path, "r") as f: |
| 182 | + config_content = f.read() |
| 183 | + |
| 184 | + # Display the content |
| 185 | + console.print("[bold]Current configuration:[/]") |
| 186 | + panel = Panel(config_content, title=f"{client_name} Config", expand=False) |
| 187 | + console.print(panel) |
| 188 | + |
| 189 | + # Count the configured servers |
| 190 | + try: |
| 191 | + config_json = json.loads(config_content) |
| 192 | + server_count = len(config_json.get("mcpServers", {})) |
| 193 | + console.print(f"[bold]Configured servers:[/] {server_count}") |
| 194 | + |
| 195 | + # Display detailed information for each server |
| 196 | + if server_count > 0: |
| 197 | + console.print("\n[bold]MCP Server Details:[/]") |
| 198 | + for server_name, server_config in config_json.get("mcpServers", {}).items(): |
| 199 | + print_server_config(server_name, server_config) |
| 200 | + |
| 201 | + except json.JSONDecodeError: |
| 202 | + console.print("[yellow]Warning: Config file contains invalid JSON[/]") |
| 203 | + |
| 204 | + except Exception as e: |
| 205 | + print_error("Error reading config file", str(e)) |
| 206 | + |
| 207 | + # Prompt to edit if file exists |
| 208 | + should_edit = False |
| 209 | + if config_exists: |
| 210 | + should_edit = Confirm.ask("Would you like to open this file in your default editor?") |
| 211 | + |
| 212 | + # Open in default editor if requested |
| 213 | + if should_edit and config_exists: |
| 214 | + try: |
| 215 | + console.print("[bold green]Opening config file in your default editor...[/]") |
| 216 | + |
| 217 | + # Use appropriate command based on platform |
| 218 | + if os.name == "nt": # Windows |
| 219 | + os.startfile(config_path) |
| 220 | + elif os.name == "posix": # macOS and Linux |
| 221 | + subprocess.run(["open", config_path] if os.uname().sysname == "Darwin" else ["xdg-open", config_path]) |
| 222 | + |
| 223 | + console.print(f"[italic]After editing, {client_name} must be restarted for changes to take effect.[/]") |
| 224 | + except Exception as e: |
| 225 | + print_error("Error opening editor", str(e)) |
| 226 | + console.print(f"You can manually edit the file at: {config_path}") |
0 commit comments