2020from mcpm .profile .profile_config import ProfileConfigManager
2121from mcpm .schemas .full_server_config import FullServerConfig
2222from mcpm .utils .config import NODE_EXECUTABLES , ConfigManager
23+ from mcpm .utils .non_interactive import is_explicit_non_interactive , should_force_operation
2324from mcpm .utils .repository import RepositoryManager
2425from mcpm .utils .rich_click_config import click
2526
@@ -88,26 +89,44 @@ def _replace_node_executable(server_config: ServerConfig) -> ServerConfig:
8889def global_add_server (server_config : ServerConfig , force : bool = False ) -> bool :
8990 """Add a server to the global MCPM configuration."""
9091 if global_config_manager .server_exists (server_config .name ) and not force :
91- console .print (f"[bold red]Error:[/] Server '{ server_config .name } ' already exists in global configuration." )
92+ console .print (f"[bold red]Error:[/ ] Server '{ server_config .name } ' already exists in global configuration." )
9293 console .print ("Use --force to override." )
9394 return False
9495
9596 server_config = _replace_node_executable (server_config )
9697 return global_config_manager .add_server (server_config , force )
9798
9899
99- def prompt_with_default (prompt_text , default = "" , hide_input = False , required = False ):
100+ def prompt_with_default (prompt_text , default = "" , hide_input = False , required = False , force = False ):
100101 """Prompt the user with a default value that can be edited directly.
101102
103+ In non-interactive mode (via --force or MCPM_NON_INTERACTIVE), this function
104+ returns the default value immediately. If a required value has no default
105+ in non-interactive mode, it raises click.UsageError.
106+
102107 Args:
103108 prompt_text: The prompt text to display
104109 default: The default value to show in the prompt
105110 hide_input: Whether to hide the input (for passwords)
106111 required: Whether this is a required field
112+ force: Whether to force non-interactive mode
107113
108114 Returns:
109115 The user's input or the default value if empty
110116 """
117+ # Check for explicit non-interactive mode (Env Var) or Force flag
118+ # We use is_explicit_non_interactive() instead of the broader is_non_interactive()
119+ # because the latter includes isatty() checks. In test environments using CliRunner,
120+ # isatty() returns True, which would incorrectly skip our mocked prompts if we checked it here.
121+ # We specifically want to allow interaction in tests unless the Env Var is set.
122+ if is_explicit_non_interactive () or should_force_operation (force ):
123+ if default :
124+ return default
125+ if required :
126+ # Cannot fulfill required argument without default in non-interactive mode
127+ raise click .UsageError ("A required value has no default and cannot be prompted in non-interactive mode." )
128+ return ""
129+
111130 # if default:
112131 # console.print(f"Default: [yellow]{default}[/]")
113132
@@ -142,7 +161,7 @@ def prompt_with_default(prompt_text, default="", hide_input=False, required=Fals
142161 # Empty result for required field without default is not allowed
143162 if not result .strip () and required and not default :
144163 console .print ("[yellow]Warning: Required value cannot be empty.[/]" )
145- return prompt_with_default (prompt_text , default , hide_input , required )
164+ return prompt_with_default (prompt_text , default , hide_input , required , force )
146165
147166 return result
148167 except (KeyboardInterrupt , EOFError ):
@@ -171,12 +190,13 @@ def install(server_name, force=False, alias=None):
171190 config_name = alias or server_name
172191
173192 # All servers are installed to global configuration
174- console .print ("[yellow]Installing server to global configuration...[/]" )
193+ console_stderr = Console (stderr = True )
194+ console_stderr .print ("[yellow]Installing server to global configuration...[/]" )
175195
176196 # Get server metadata from repository
177197 server_metadata = repo_manager .get_server_metadata (server_name )
178198 if not server_metadata :
179- console .print (f"[bold red]Error:[/] Server '{ server_name } ' not found in registry." )
199+ console .print (f"[bold red]Error:[/ ] Server '{ server_name } ' not found in registry." )
180200 console .print (f"Available servers: { ', ' .join (repo_manager ._fetch_servers ().keys ())} " )
181201 return
182202
@@ -195,7 +215,10 @@ def install(server_name, force=False, alias=None):
195215
196216 # Confirm addition
197217 alias_text = f" as '{ alias } '" if alias else ""
198- if not force and not Confirm .ask (f"Install this server to global configuration{ alias_text } ?" ):
218+ # Bypass confirmation if force flag is set OR explicit non-interactive mode is enabled
219+ if not (should_force_operation (force ) or is_explicit_non_interactive ()) and not Confirm .ask (
220+ f"Install this server to global configuration{ alias_text } ?"
221+ ):
199222 console .print ("[yellow]Operation cancelled.[/]" )
200223 return
201224
@@ -239,8 +262,8 @@ def install(server_name, force=False, alias=None):
239262 method_id = next (iter (installations ))
240263 selected_method = installations [method_id ]
241264
242- # If multiple methods are available and not forced, offer selection
243- if len (installations ) > 1 and not force :
265+ # If multiple methods are available and not forced/non-interactive , offer selection
266+ if len (installations ) > 1 and not ( should_force_operation ( force ) or is_explicit_non_interactive ()) :
244267 console .print ("\n [bold]Available installation methods:[/]" )
245268 methods_list = []
246269
@@ -349,6 +372,7 @@ def install(server_name, force=False, alias=None):
349372 default = env_value ,
350373 hide_input = _should_hide_input (arg_name ),
351374 required = is_required ,
375+ force = force ,
352376 )
353377 if user_value != env_value :
354378 # User provided a different value
@@ -366,6 +390,7 @@ def install(server_name, force=False, alias=None):
366390 default = example if example else "" ,
367391 hide_input = _should_hide_input (arg_name ),
368392 required = is_required ,
393+ force = force ,
369394 )
370395
371396 # Only add non-empty values to the environment
@@ -419,7 +444,7 @@ def install(server_name, force=False, alias=None):
419444 if has_non_standard_argument_define :
420445 # no matter in argument / env
421446 console .print (
422- "[bold yellow]WARNING:[/] [bold]Non-standard argument format detected in server configuration.[/]\n "
447+ "[bold yellow]WARNING:[/ ] [bold]Non-standard argument format detected in server configuration.[/]\n "
423448 "[bold cyan]Future versions of MCPM will standardize all arguments in server configuration to use ${VARIABLE_NAME} format.[/]\n "
424449 "[bold]Please verify that your input arguments are correctly recognized.[/]\n "
425450 )
@@ -460,7 +485,7 @@ def install(server_name, force=False, alias=None):
460485 )
461486
462487 # Add server to global configuration
463- success = global_add_server (full_server_config .to_server_config (), force )
488+ success = global_add_server (full_server_config .to_server_config (), should_force_operation ( force ) )
464489
465490 if success :
466491 # Server has been successfully added to the global configuration
@@ -523,7 +548,7 @@ def extract_from_value(value):
523548
524549 # Check all fields in the installation method
525550 for key , value in installation_method .items ():
526- if key not in ["type" , "description" , "recommended" ]: # Skip metadata fields
551+ if key not in ["type" , "description" , "recommended" ]:
527552 extract_from_value (value )
528553
529554 return referenced
0 commit comments