Skip to content

feat: improve profile management #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -114,25 +113,22 @@ 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
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).

Expand Down Expand Up @@ -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
Expand Down
11 changes: 3 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ mcpm --version # 显示 MCPM 的当前版本

```bash
mcpm client ls # 列出所有支持的 MCP 客户端,检测已安装的客户端,并显示活动客户端
mcpm client set CLIENT # 为后续命令设置活动客户端
mcpm client edit # 在外部编辑器中打开活动客户端的 MCP 配置文件
```

Expand Down Expand Up @@ -148,25 +147,21 @@ mcpm pop [SERVER_NAME] # 恢复最后暂存的服务器,或按名称恢复

配置文件是服务器配置的命名集合。它们允许您轻松切换不同的 MCP 服务器集。例如,您可能有一个 `work` 配置文件和一个 `personal` 配置文件,每个都包含不同的服务器。或者,您可能有一个 `production` 配置文件和一个 `development` 配置文件,每个都包含同一服务器的不同配置。

当前*活动*配置文件的服务器通常由 MCPM 路由器等功能使用。使用 `mcpm activate` 设置活动配置文件。
当前*活动*配置文件的服务器通常由 MCPM 路由器等功能使用。使用 `mcpm target set %profile_name` 设置活动配置文件。

```bash
# 🔄 配置文件生命周期
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) 中了解有关这些高级功能的更多信息。

Expand Down Expand Up @@ -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 服务器
Expand Down
18 changes: 5 additions & 13 deletions pages/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -871,14 +871,6 @@ <h3><span><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox
</div>
</div>

<div class="feature">
<h3><span><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg></span> Activate Profiles</h3>
<p>Switch between different sets of server configurations:</p>
<div class="code-block">
<code><span class="command-prompt">$</span> mcpm activate work</code>
<button class="copy-button" data-command="mcpm activate work"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>
</div>
</div>

<div class="feature">
<h3><span><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg></span> List Profiles</h3>
Expand Down Expand Up @@ -1013,10 +1005,10 @@ <h2>Client Management</h2>
<div class="features">
<div class="feature">
<h3><span><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg></span> Set Active Client</h3>
<p>Easily switch between and manage MCP clients:</p>
<p>Easily switch between and manage MCP clients/profiles:</p>
<div class="code-block">
<code><span class="command-prompt">$</span> mcpm client set claude-desktop</code>
<button class="copy-button" data-command="mcpm client set claude-desktop"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>
<code><span class="command-prompt">$</span> mcpm target set @claude-desktop</code>
<button class="copy-button" data-command="mcpm target set @claude-desktop"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>
</div>
</div>

Expand Down Expand Up @@ -1100,10 +1092,10 @@ <h2>Supported Clients</h2>
" 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;
Expand Down
50 changes: 29 additions & 21 deletions src/mcpm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
router,
search,
stash,
target,
transfer,
)

Expand Down Expand Up @@ -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 <client-name>' 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 <target>' 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 @<client>':[/]")
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>':[/]")
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 <client-name>' to set one.")
console.print("[bold red]No active target set![/] Please run 'mcpm target set <target>' to set one.")
console.print("")

# Display usage info
Expand All @@ -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.")
Expand All @@ -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.")
Expand All @@ -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")

Expand Down
79 changes: 24 additions & 55 deletions src/mcpm/clients/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand All @@ -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"""
Expand Down
Loading
Loading