Skip to content

Commit 2e80662

Browse files
feat: profile management (#28)
* feat: profile management * feat: add show command
1 parent 558e353 commit 2e80662

File tree

5 files changed

+311
-41
lines changed

5 files changed

+311
-41
lines changed

src/mcpm/cli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
inspector,
1717
list,
1818
pop,
19+
profile,
1920
remove,
2021
search,
2122
stash,
@@ -130,6 +131,7 @@ def main(ctx, help_flag):
130131
commands_table.add_row(" [cyan]search[/]", "Search available MCP servers.")
131132
commands_table.add_row(" [cyan]stash[/]", "Temporarily store a server configuration aside.")
132133
commands_table.add_row(" [cyan]pop[/]", "Restore a previously stashed server configuration.")
134+
commands_table.add_row(" [cyan]profile[/]", "Manage MCPM profiles.")
133135
console.print(commands_table)
134136

135137
# Additional helpful information
@@ -150,6 +152,7 @@ def main(ctx, help_flag):
150152
main.add_command(client.client)
151153
main.add_command(config.config)
152154
main.add_command(inspector.inspector, name="inspector")
155+
main.add_command(profile.profile, name="profile")
153156

154157
if __name__ == "__main__":
155158
main()

src/mcpm/commands/__init__.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@
22
MCPM commands package
33
"""
44

5-
__all__ = ["add", "client", "edit", "inspector", "list", "pop", "remove", "search", "stash"]
5+
__all__ = ["add", "client", "edit", "inspector", "list", "pop", "profile", "remove", "search", "stash"]
66

77
# All command modules
8-
from . import add
9-
from . import client
10-
from . import edit
11-
from . import inspector
12-
from . import list
13-
from . import pop
14-
from . import remove
15-
from . import search
16-
17-
from . import stash
8+
from . import add, client, edit, inspector, list, pop, profile, remove, search, stash

src/mcpm/commands/add.py

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,73 @@
1212
from rich.prompt import Confirm
1313

1414
from mcpm.clients.client_registry import ClientRegistry
15+
from mcpm.profile.profile_manager import ProfileManager
1516
from mcpm.utils.repository import RepositoryManager
1617
from mcpm.utils.server_config import ServerConfig
1718

1819
console = Console()
1920
repo_manager = RepositoryManager()
21+
profile_manager = ProfileManager()
2022

2123

2224
@click.command()
2325
@click.argument("server_name")
2426
@click.option("--force", is_flag=True, help="Force reinstall if server is already installed")
2527
@click.option("--alias", help="Alias for the server", required=False)
26-
def add(server_name, force=False, alias=None):
28+
@click.option("--profile", help="Profile to add server to", required=False)
29+
def add(server_name, force=False, alias=None, profile=None):
2730
"""Add an MCP server to a client configuration.
2831
2932
Examples:
3033
mcpm add time
3134
mcpm add everything --force
3235
mcpm add youtube --alias yt
36+
mcpm add youtube --profile myprofile
3337
"""
34-
# Get the active client info
35-
client = ClientRegistry.get_active_client()
36-
if not client:
37-
console.print("[bold red]Error:[/] No active client found.")
38-
console.print("Please set an active client with 'mcpm client set <client>'.")
39-
return
40-
console.print(f"[yellow]Using active client: {client}[/]")
41-
42-
# Get client manager
43-
client_manager = ClientRegistry.get_active_client_manager()
44-
if client_manager is None:
45-
console.print(f"[bold red]Error:[/] Unsupported client '{client}'.")
46-
return
47-
4838
config_name = alias or server_name
4939

50-
# Check if server already exists in client config
51-
existing_server = client_manager.get_server(config_name)
52-
if existing_server and not force:
53-
console.print(f"[yellow]Server '{config_name}' is already added to {client}.[/]")
54-
console.print("Use '--force' to overwrite the existing configuration.")
55-
return
40+
if profile is None:
41+
# Get the active client info
42+
client = ClientRegistry.get_active_client()
43+
if not client:
44+
console.print("[bold red]Error:[/] No active client found.")
45+
console.print("Please set an active client with 'mcpm client set <client>'.")
46+
return
47+
console.print(f"[yellow]Using active client: {client}[/]")
48+
49+
# Get client manager
50+
client_manager = ClientRegistry.get_active_client_manager()
51+
if client_manager is None:
52+
console.print(f"[bold red]Error:[/] Unsupported client '{client}'.")
53+
return
54+
55+
# Check if server already exists in client config
56+
existing_server = client_manager.get_server(config_name)
57+
if existing_server and not force:
58+
console.print(f"[yellow]Server '{config_name}' is already added to {client}.[/]")
59+
console.print("Use '--force' to overwrite the existing configuration.")
60+
return
61+
62+
target_display_name = client_manager.display_name
63+
else:
64+
# Get profile
65+
profile_info = profile_manager.get_profile(profile)
66+
if profile_info is None:
67+
console.print(f"[yellow]Profile '{profile}' not found. Create new profile? [bold]y/n[/]", end=" ")
68+
if not Confirm.ask():
69+
return
70+
profile_manager.new_profile(profile)
71+
console.print(f"[green]Profile '{profile}' added successfully.[/]\n")
72+
profile_info = []
73+
74+
# Check if server already exists in client config
75+
for server in profile_info:
76+
if server.name == config_name and not force:
77+
console.print(f"[yellow]Server '{config_name}' is already added to {client}.[/]")
78+
console.print("Use '--force' to overwrite the existing configuration.")
79+
return
80+
81+
target_display_name = profile
5682

5783
# Get server metadata from repository
5884
server_metadata = repo_manager.get_server_metadata(server_name)
@@ -74,12 +100,8 @@ def add(server_name, force=False, alias=None):
74100
author_url = author_info.get("url", "")
75101
console.print(f"[dim]Author: {author_name} {author_url}[/]")
76102

77-
# Get client display name from the utility
78-
client_info = ClientRegistry.get_client_info(client)
79-
client_display_name = client_info.get("name", client)
80-
81103
# Confirm addition
82-
if not force and not Confirm.ask(f"Add this server to {client_display_name}{' as ' + alias if alias else ''}?"):
104+
if not force and not Confirm.ask(f"Add this server to {target_display_name}{' as ' + alias if alias else ''}?"):
83105
console.print("[yellow]Operation cancelled.[/]")
84106
return
85107

@@ -333,12 +355,16 @@ def add(server_name, force=False, alias=None):
333355
installation=installation_method,
334356
)
335357

336-
# Add the server to the client configuration
337-
success = client_manager.add_server(server_config)
358+
if not profile:
359+
# Add the server to the client configuration
360+
success = client_manager.add_server(server_config)
361+
else:
362+
# Add the server to the profile configuration
363+
success = profile_manager.set_profile(profile, server_config)
338364

339365
if success:
340366
# Server has been successfully added to the client configuration
341-
console.print(f"[bold green]Successfully added {display_name} to {client_display_name}![/]")
367+
console.print(f"[bold green]Successfully added {display_name} to {target_display_name}![/]")
342368

343369
# Display usage examples if available
344370
examples = server_metadata.get("examples", [])
@@ -353,4 +379,4 @@ def add(server_name, force=False, alias=None):
353379
if prompt:
354380
console.print(f' Try: [italic]"{prompt}"[/]\n')
355381
else:
356-
console.print(f"[bold red]Failed to add {server_name} to {client}.[/]")
382+
console.print(f"[bold red]Failed to add {server_name} to {target_display_name}.[/]")

src/mcpm/commands/profile.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import click
2+
from rich.console import Console
3+
from rich.markup import escape
4+
from rich.table import Table
5+
6+
from mcpm.clients.client_registry import ClientRegistry
7+
from mcpm.profile.profile_manager import ProfileManager
8+
9+
profile_manager = ProfileManager()
10+
console = Console()
11+
12+
13+
@click.group()
14+
def profile():
15+
"""Manage MCPM profiles."""
16+
pass
17+
18+
19+
@click.command()
20+
@click.option("--verbose", "-v", is_flag=True, help="Show detailed server information")
21+
def list(verbose=False):
22+
"""List all MCPM profiles."""
23+
profiles = profile_manager.list_profiles()
24+
if not profiles:
25+
console.print("\n[yellow]No profiles found.[/]\n")
26+
return
27+
console.print(f"\n[green]Found {len(profiles)} profile(s)[/]\n")
28+
table = Table(show_header=True, header_style="bold")
29+
table.add_column("Name", style="cyan")
30+
table.add_column("Servers", overflow="fold")
31+
if verbose:
32+
table.add_column("Server Details", overflow="fold")
33+
for profile_name, configs in profiles.items():
34+
server_names = [config.name for config in configs]
35+
row = [profile_name, ", ".join(server_names)]
36+
if verbose:
37+
details = []
38+
for config in configs:
39+
details.append(f"{config.name}: {config.command} {' '.join(config.args)}")
40+
row.append("\n".join(details))
41+
table.add_row(*row)
42+
console.print(table)
43+
44+
45+
@click.command()
46+
@click.argument("profile")
47+
@click.option("--force", is_flag=True, help="Force add even if profile already exists")
48+
def add(profile, force=False):
49+
"""Add a new MCPM profile."""
50+
if profile_manager.get_profile(profile) is not None and not force:
51+
console.print(f"[bold red]Error:[/] Profile '{profile}' already exists.")
52+
console.print("Use '--force' to overwrite the existing profile.")
53+
return
54+
55+
profile_manager.new_profile(profile)
56+
57+
console.print(f"\n[green]Profile '{profile}' added successfully.[/]\n")
58+
console.print(f"You can now add servers to this profile with 'mcpm add --profile {profile} <server_name>'\n")
59+
console.print(
60+
f"Or apply existing config to this profile with 'mcpm profile apply {profile} --server <server_name>'\n"
61+
)
62+
63+
64+
@click.command()
65+
@click.argument("profile")
66+
@click.option("--server", "-s", required=True, help="Server to apply config to")
67+
def apply(profile, server):
68+
"""Apply an existing MCPM config to a profile."""
69+
client_manager = ClientRegistry.get_active_client_manager()
70+
client = ClientRegistry.get_active_client()
71+
client_info = ClientRegistry.get_client_info(client)
72+
client_name = client_info.get("name", client)
73+
74+
# Check if client is supported
75+
if client_manager is None:
76+
console.print("[bold red]Error:[/] Unsupported active client")
77+
console.print("Please switch to a supported client using 'mcpm client <client-name>'")
78+
return
79+
80+
# Check if the server exists in the active client
81+
server_info = client_manager.get_server(server)
82+
if server_info is None:
83+
console.print(f"[bold red]Error:[/] Server '{server}' not found in {client_name}.")
84+
return
85+
86+
# Get profile
87+
profile_info = profile_manager.get_profile(profile)
88+
if profile_info is None:
89+
console.print(f"[bold red]Error:[/] Profile '{profile}' not found.")
90+
return
91+
92+
# Save profile
93+
profile_manager.set_profile(profile, server_info)
94+
console.print(f"\n[green]Server '{server}' applied to profile '{profile}' successfully.[/]\n")
95+
96+
97+
@click.command()
98+
@click.argument("profile_name")
99+
def delete(profile_name):
100+
"""Delete an MCPM profile."""
101+
if not profile_manager.delete_profile(profile_name):
102+
console.print(f"[bold red]Error:[/] Profile '{profile_name}' not found.")
103+
return
104+
console.print(f"\n[green]Profile '{profile_name}' deleted successfully.[/]\n")
105+
106+
107+
@click.command()
108+
@click.argument("profile_name")
109+
def rename(profile_name):
110+
"""Rename an MCPM profile."""
111+
new_profile_name = click.prompt("Enter new profile name", type=str)
112+
if profile_manager.get_profile(new_profile_name) is not None:
113+
console.print(f"[bold red]Error:[/] Profile '{new_profile_name}' already exists.")
114+
return
115+
if not profile_manager.rename_profile(profile_name, new_profile_name):
116+
console.print(f"[bold red]Error:[/] Profile '{profile_name}' not found.")
117+
return
118+
console.print(f"\n[green]Profile '{profile_name}' renamed to '{new_profile_name}' successfully.[/]\n")
119+
120+
121+
@click.command()
122+
@click.argument("profile")
123+
@click.option("--server", "-s", required=True, help="Server to remove from profile")
124+
def remove_server(server, profile):
125+
"""Remove a server from an MCPM profile."""
126+
if not profile_manager.remove_server(server, profile):
127+
console.print(f"[bold red]Error:[/] Server '{server}' not found in profile '{profile}'.")
128+
return
129+
console.print(f"\n[green]Server '{server}' removed from profile '{profile}' successfully.[/]\n")
130+
131+
132+
@click.command()
133+
@click.argument("profile")
134+
def show(profile):
135+
"""Show the servers in an MCPM profile."""
136+
profile_info = profile_manager.get_profile(profile)
137+
if profile_info is None:
138+
console.print(f"[bold red]Error:[/] Profile '{profile}' not found.")
139+
return
140+
console.print(f"\n[green]Profile '{profile}' contains the following servers:[/]\n")
141+
for server in profile_info:
142+
console.print(f"[bold cyan]{server.name}[/]")
143+
command = server.command
144+
console.print(f" Command: [green]{command}[/]")
145+
146+
# Display arguments
147+
args = server.args
148+
if args:
149+
console.print(" Arguments:")
150+
for i, arg in enumerate(args):
151+
console.print(f" {i}: [yellow]{escape(arg)}[/]")
152+
153+
# Display environment variables
154+
env_vars = server.env
155+
if env_vars:
156+
console.print(" Environment Variables:")
157+
for key, value in env_vars.items():
158+
console.print(f' [bold blue]{key}[/] = [green]"{value}"[/]')
159+
else:
160+
console.print(" Environment Variables: [italic]None[/]")
161+
162+
# Add a separator line between servers
163+
console.print(" " + "-" * 50)
164+
console.print("\n")
165+
166+
167+
# Register all commands with the profile group
168+
profile.add_command(list)
169+
profile.add_command(add)
170+
profile.add_command(show)
171+
profile.add_command(apply)
172+
profile.add_command(delete)
173+
profile.add_command(rename)
174+
profile.add_command(remove_server, name="rm-server")

0 commit comments

Comments
 (0)