1515from rich .progress import Progress , SpinnerColumn , TextColumn
1616from rich .prompt import Confirm
1717
18- from mcpm .commands . target_operations . common import (
19- global_add_server ,
20- )
18+ from mcpm .global_config import GlobalConfigManager
19+ from mcpm . core . schema import ServerConfig , STDIOServerConfig
20+ from mcpm . utils . config import NODE_EXECUTABLES , ConfigManager
2121from mcpm .profile .profile_config import ProfileConfigManager
2222from mcpm .schemas .full_server_config import FullServerConfig
2323from mcpm .utils .repository import RepositoryManager
2626console = Console ()
2727repo_manager = RepositoryManager ()
2828profile_config_manager = ProfileConfigManager ()
29+ global_config_manager = GlobalConfigManager ()
2930
3031# Create a prompt session with custom styling
3132prompt_session = PromptSession ()
4344kb = KeyBindings ()
4445
4546
47+ def _replace_node_executable (server_config : ServerConfig ) -> ServerConfig :
48+ """Replace node executable with configured alternative if applicable."""
49+ if not isinstance (server_config , STDIOServerConfig ):
50+ return server_config
51+ command = server_config .command .strip ()
52+ if command not in NODE_EXECUTABLES :
53+ return server_config
54+ config = ConfigManager ().get_config ()
55+ config_node_executable = config .get ("node_executable" )
56+ if not config_node_executable :
57+ return server_config
58+ if config_node_executable != command :
59+ console .print (f"[bold cyan]Replace node executable { command } with { config_node_executable } [/]" )
60+ server_config .command = config_node_executable
61+ return server_config
62+
63+
64+ def global_add_server (server_config : ServerConfig , force : bool = False ) -> bool :
65+ """Add a server to the global MCPM configuration."""
66+ if global_config_manager .server_exists (server_config .name ) and not force :
67+ console .print (f"[bold red]Error:[/] Server '{ server_config .name } ' already exists in global configuration." )
68+ console .print ("Use --force to override." )
69+ return False
70+
71+ server_config = _replace_node_executable (server_config )
72+ return global_config_manager .add_server (server_config , force )
73+
74+
4675def prompt_with_default (prompt_text , default = "" , hide_input = False , required = False ):
4776 """Prompt the user with a default value that can be edited directly.
4877
@@ -91,7 +120,7 @@ def prompt_with_default(prompt_text, default="", hide_input=False, required=Fals
91120@click .option ("--force" , is_flag = True , help = "Force reinstall if server is already installed" )
92121@click .option ("--alias" , help = "Alias for the server" , required = False )
93122@click .help_option ("-h" , "--help" )
94- def add (server_name , force = False , alias = None ):
123+ def install (server_name , force = False , alias = None ):
95124 """Install an MCP server to the global configuration.
96125
97126 Installs servers to the global MCPM configuration where they can be
@@ -100,22 +129,14 @@ def add(server_name, force=False, alias=None):
100129 Examples:
101130
102131 \b
103- mcpm add time
104- mcpm add everything --force
105- mcpm add youtube --alias yt
132+ mcpm install time
133+ mcpm install everything --force
134+ mcpm install youtube --alias yt
106135 """
107136
108- # v2.0: use global config
109-
110- # Check if this is a profile (starts with %)
111- if server_name .startswith ("%" ):
112- profile_name = server_name [1 :] # Remove % prefix
113- add_profile_to_client (profile_name , "global" , alias , force )
114- return
115-
116137 config_name = alias or server_name
117138
118- # v2.0: All servers are installed to global configuration
139+ # All servers are installed to global configuration
119140 console .print ("[yellow]Installing server to global configuration...[/]" )
120141
121142 # Get server metadata from repository
@@ -240,12 +261,30 @@ def add(server_name, force=False, alias=None):
240261 # Process variables to store in config
241262 processed_variables = {}
242263
243- # First, prompt for all defined arguments even if they're not in env_vars
244- progress .stop ()
264+ # Extract which arguments are actually referenced in the selected installation method
265+ referenced_vars = _extract_referenced_variables (selected_method ) if selected_method else set ()
266+
267+ # Filter arguments to only those that are referenced
268+ relevant_arguments = {}
245269 if all_arguments :
270+ if referenced_vars :
271+ # Only include arguments that are referenced
272+ for arg_name , arg_info in all_arguments .items ():
273+ if arg_name in referenced_vars :
274+ relevant_arguments [arg_name ] = arg_info
275+ elif selected_method :
276+ # If we have a selected method but no referenced vars, don't prompt for any
277+ relevant_arguments = {}
278+ else :
279+ # No selected method - use all arguments (backward compatibility)
280+ relevant_arguments = all_arguments
281+
282+ # First, prompt for relevant arguments
283+ progress .stop ()
284+ if relevant_arguments :
246285 console .print ("\n [bold]Configure server arguments:[/]" )
247286
248- for arg_name , arg_info in all_arguments .items ():
287+ for arg_name , arg_info in relevant_arguments .items ():
249288 description = arg_info .get ("description" , "" )
250289 is_required = arg_info .get ("required" , False )
251290 example = arg_info .get ("example" , "" )
@@ -328,6 +367,21 @@ def add(server_name, force=False, alias=None):
328367 if key in processed_variables and replacement_status == ReplacementStatus .NON_STANDARD_REPLACE :
329368 has_non_standard_argument_define = True
330369
370+ # For HTTP servers, headers should be extracted from the installation method
371+ # not from processed variables
372+ processed_headers = {}
373+ if installation_method == "http" and selected_method :
374+ # Extract headers from the installation method if defined
375+ headers_template = selected_method .get ("headers" , {})
376+ for key , value in headers_template .items ():
377+ # Replace variables in header values
378+ header_replaced , _ = _replace_variables (value , processed_variables )
379+ if header_replaced :
380+ processed_headers [key ] = header_replaced
381+ else :
382+ # If no replacement, use the original value
383+ processed_headers [key ] = value
384+
331385 if has_non_standard_argument_define :
332386 # no matter in argument / env
333387 console .print (
@@ -338,11 +392,22 @@ def add(server_name, force=False, alias=None):
338392
339393 # Get actual MCP execution command, args, and env from the selected installation method
340394 # This ensures we use the actual server command information instead of placeholders
395+ mcp_url = None
396+ mcp_command = None
397+ mcp_args = []
398+
341399 if selected_method :
342- mcp_command = selected_method .get ("command" , install_command )
343- mcp_args = processed_args
400+ # For HTTP servers, extract the URL and don't set command/args
401+ if installation_method == "http" :
402+ mcp_url = selected_method .get ("url" )
403+ # HTTP servers don't have command/args
404+ else :
405+ # For non-HTTP servers, get command and args
406+ mcp_command = selected_method .get ("command" , install_command )
407+ mcp_args = processed_args
344408 # Env vars are already processed above
345409 else :
410+ # Fallback for when no selected method
346411 mcp_command = install_command
347412 mcp_args = processed_args
348413
@@ -356,9 +421,11 @@ def add(server_name, force=False, alias=None):
356421 env = processed_env ,
357422 # Use the simplified installation method
358423 installation = installation_method ,
424+ url = mcp_url , # Include URL for HTTP servers
425+ headers = processed_headers , # Include headers for HTTP servers
359426 )
360427
361- # v2.0: Add server to global configuration
428+ # Add server to global configuration
362429 success = global_add_server (full_server_config .to_server_config (), force )
363430
364431 if success :
@@ -393,6 +460,41 @@ def _should_hide_input(arg_name: str) -> bool:
393460 return "token" in arg_name .lower () or "key" in arg_name .lower () or "secret" in arg_name .lower ()
394461
395462
463+ def _extract_referenced_variables (installation_method : dict ) -> set :
464+ """Extract all variable names referenced in an installation method.
465+
466+ Scans through all fields in the installation method (command, args, env, url, etc.)
467+ looking for ${VARIABLE_NAME} patterns.
468+
469+ Args:
470+ installation_method: The installation method configuration dict
471+
472+ Returns:
473+ Set of variable names that are referenced
474+ """
475+ referenced = set ()
476+
477+ def extract_from_value (value ):
478+ """Recursively extract variables from a value."""
479+ if isinstance (value , str ):
480+ # Find all ${VAR_NAME} patterns
481+ matches = re .findall (r"\$\{([^}]+)\}" , value )
482+ referenced .update (matches )
483+ elif isinstance (value , list ):
484+ for item in value :
485+ extract_from_value (item )
486+ elif isinstance (value , dict ):
487+ for v in value .values ():
488+ extract_from_value (v )
489+
490+ # Check all fields in the installation method
491+ for key , value in installation_method .items ():
492+ if key not in ["type" , "description" , "recommended" ]: # Skip metadata fields
493+ extract_from_value (value )
494+
495+ return referenced
496+
497+
396498class ReplacementStatus (str , Enum ):
397499 NOT_REPLACED = "not_replaced"
398500 STANDARD_REPLACE = "standard_replace"
@@ -473,18 +575,3 @@ def _replace_argument_variables(value: str, prev_value: str, variables: dict) ->
473575
474576 # nothing to replace
475577 return value , ReplacementStatus .NOT_REPLACED
476-
477-
478- def add_profile_to_client (profile_name : str , client : str , alias : str | None = None , force : bool = False ):
479- if not force and not Confirm .ask (f"Add this profile { profile_name } to { client } { ' as ' + alias if alias else '' } ?" ):
480- console .print ("[yellow]Operation cancelled.[/]" )
481- raise click .ClickException ("Operation cancelled" )
482-
483- console .print ("[bold red]Error:[/] Profile activation has been removed in MCPM v2.0." )
484- console .print ("[yellow]Use 'mcpm profile share' to share profiles instead.[/]" )
485- success = False
486- if success :
487- console .print (f"[bold green]Successfully added profile { profile_name } to { client } ![/]" )
488- else :
489- console .print (f"[bold red]Failed to add profile { profile_name } to { client } .[/]" )
490- raise click .ClickException (f"Failed to add profile { profile_name } to { client } " )
0 commit comments