-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathcli.py
More file actions
148 lines (124 loc) · 4.67 KB
/
Copy pathcli.py
File metadata and controls
148 lines (124 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
MCPM CLI - Main entry point for the Model Context Protocol Manager CLI
"""
# Import rich-click configuration before anything else
from typing import Any, Dict
from rich.console import Console
from rich.traceback import Traceback
from rich.traceback import install as install_rich_traceback
from mcpm.clients.client_config import ClientConfigManager
from mcpm.commands import (
client,
config,
doctor,
edit,
info,
inspect,
install,
list,
migrate,
new,
profile,
run,
search,
uninstall,
usage,
)
from mcpm.commands.share import share
from mcpm.migration import V1ConfigDetector, V1ToV2Migrator
from mcpm.utils.logging_config import setup_logging
from mcpm.utils.rich_click_config import click, get_header_text
console = Console() # stdout for regular CLI output
err_console = Console(stderr=True) # stderr for errors/tracebacks
client_config_manager = ClientConfigManager()
# Setup Rich logging early - this runs when the module is imported
setup_logging()
# Install Rich's global exception handler to use stderr instead of stdout
# This prevents Rich/rich-gradient from routing tracebacks to stdout
install_rich_traceback(console=err_console, show_locals=True)
# Custom context settings to handle main command help specially
CONTEXT_SETTINGS: Dict[str, Any] = dict(help_option_names=[])
def print_logo():
"""Print an elegant gradient logo with invisible Panel for width control"""
console.print(get_header_text())
def handle_exceptions(func):
"""Decorator to catch unhandled exceptions and provide a helpful error message."""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
err_console.print(Traceback(show_locals=True))
err_console.print("[bold red]An unexpected error occurred.[/bold red]")
err_console.print(
"Please report this issue on our GitHub repository: "
"[link=https://github.com/pathintegral-institute/mcpm.sh/issues]https://github.com/pathintegral-institute/mcpm.sh/issues[/link]"
)
return wrapper
@click.group(
name="mcpm",
context_settings=CONTEXT_SETTINGS,
invoke_without_command=True,
help="""
Centralized MCP server management - discover, install, run, and share servers.
Manage servers globally, organize with profiles, monitor usage, and integrate
with all MCP clients.
""",
)
@click.option("-v", "--version", is_flag=True, help="Show version and exit.")
@click.option("-h", "--help", "help_flag", is_flag=True, help="Show this message and exit.")
@click.pass_context
@handle_exceptions
def main(ctx, version, help_flag):
"""Main entry point for MCPM CLI."""
if version:
print_logo()
return
if help_flag:
# Show custom help with header and footer for main command only
console.print(get_header_text())
# Temporarily disable global footer to avoid duplication
original_footer = click.rich_click.FOOTER_TEXT
click.rich_click.FOOTER_TEXT = None
click.echo(ctx.get_help())
click.rich_click.FOOTER_TEXT = original_footer
return
# Check for v1 configuration and offer migration (even with subcommands)
detector = V1ConfigDetector()
if detector.has_v1_config():
migrator = V1ToV2Migrator()
migration_choice = migrator.show_migration_prompt()
if migration_choice == "migrate":
migrator.migrate_config()
return
elif migration_choice == "start_fresh":
migrator.start_fresh()
# Continue to execute the subcommand
# If "ignore", continue to subcommand without migration
# If no command was invoked, show help with header and footer
if ctx.invoked_subcommand is None:
console.print(get_header_text())
# Temporarily disable global footer to avoid duplication
original_footer = click.rich_click.FOOTER_TEXT
click.rich_click.FOOTER_TEXT = None
click.echo(ctx.get_help())
click.rich_click.FOOTER_TEXT = original_footer
# Register v2.0 commands
main.add_command(search.search)
main.add_command(info.info)
main.add_command(list.list, name="ls")
main.add_command(install.install)
main.add_command(uninstall.uninstall)
main.add_command(edit.edit)
main.add_command(new.new)
main.add_command(run.run)
main.add_command(inspect.inspect)
main.add_command(profile.profile, name="profile")
main.add_command(doctor.doctor)
main.add_command(usage.usage)
main.add_command(config.config)
main.add_command(migrate.migrate)
main.add_command(share)
# Keep these for now but they could be simplified later
main.add_command(client.client)
if __name__ == "__main__":
main()