Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/mcpm/clients/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging
import os
import platform
import re
from typing import Any, Dict, List, Optional, Union

from pydantic import TypeAdapter
Expand Down Expand Up @@ -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):
"""
Expand Down
3 changes: 3 additions & 0 deletions src/mcpm/clients/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/mcpm/clients/managers/claude_desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions src/mcpm/clients/managers/continue_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions src/mcpm/clients/managers/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
6 changes: 2 additions & 4 deletions src/mcpm/clients/managers/goose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions src/mcpm/clients/managers/windsurf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
13 changes: 11 additions & 2 deletions src/mcpm/commands/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)

Expand All @@ -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()

Expand All @@ -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(
Expand Down
54 changes: 51 additions & 3 deletions src/mcpm/commands/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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}'.")

Expand All @@ -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:
Expand All @@ -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}'.")

Expand Down Expand Up @@ -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")


Expand All @@ -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")
1 change: 1 addition & 0 deletions src/mcpm/commands/server_operations/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down