55import json
66import os
77import re
8+ from enum import Enum
89
910import click
1011from prompt_toolkit import PromptSession
@@ -344,13 +345,31 @@ def add(server_name, force=False, alias=None, target: str | None = None):
344345
345346 # replace arguments with values
346347 processed_args = []
347- for arg in install_args :
348- processed_args .append (_replace_variables (arg , processed_variables ))
348+ has_non_standard_argument_define = False
349+ for i , arg in enumerate (install_args ):
350+ prev_arg = install_args [i - 1 ] if i > 0 else ""
351+ # handles arguments with pattern var=${var} | --var=var | --var var
352+ arg_replaced , replacement_status = _replace_argument_variables (arg , prev_arg , processed_variables )
353+ processed_args .append (arg_replaced )
354+ if replacement_status == ReplacementStatus .NON_STANDARD_REPLACE :
355+ has_non_standard_argument_define = True
349356
350357 # process environment variables
351358 processed_env = {}
352359 for key , value in env_vars .items ():
353- processed_env [key ] = _replace_variables (value , processed_variables )
360+ # just replace the env value regardless of the variable pattern, ${VAR}/YOUR_VAR/VAR
361+ env_replaced , replacement_status = _replace_variables (value , processed_variables )
362+ processed_env [key ] = env_replaced if env_replaced else processed_variables .get (key , value )
363+ if key in processed_variables and replacement_status == ReplacementStatus .NON_STANDARD_REPLACE :
364+ has_non_standard_argument_define = True
365+
366+ if has_non_standard_argument_define :
367+ # no matter in argument / env
368+ console .print (
369+ "[bold yellow]WARNING:[/] [bold]Non-standard argument format detected in server configuration.[/]\n "
370+ "[bold cyan]Future versions of MCPM will standardize all arguments in server configuration to use ${VARIABLE_NAME} format.[/]\n "
371+ "[bold]Please verify that your input arguments are correctly recognized.[/]\n "
372+ )
354373
355374 # Get actual MCP execution command, args, and env from the selected installation method
356375 # This ensures we use the actual server command information instead of placeholders
@@ -413,7 +432,13 @@ def _should_hide_input(arg_name: str) -> bool:
413432 return "token" in arg_name .lower () or "key" in arg_name .lower () or "secret" in arg_name .lower ()
414433
415434
416- def _replace_variables (value : str , variables : dict ) -> str :
435+ class ReplacementStatus (str , Enum ):
436+ NOT_REPLACED = "not_replaced"
437+ STANDARD_REPLACE = "standard_replace"
438+ NON_STANDARD_REPLACE = "non_standard_replace"
439+
440+
441+ def _replace_variables (value : str , variables : dict ) -> tuple [str , ReplacementStatus ]:
417442 """Replace ${VAR} patterns in a string with values from variables dict.
418443
419444 Args:
@@ -424,12 +449,66 @@ def _replace_variables(value: str, variables: dict) -> str:
424449 String with all variables replaced (empty string for missing variables)
425450 """
426451 if not isinstance (value , str ):
427- return value
452+ return value , ReplacementStatus .NOT_REPLACED
453+
454+ # check if the value contains a variable
455+ matched = re .search (r"\$\{([^}]+)\}" , value )
456+ if matched :
457+ original , var_name = matched .group (0 ), matched .group (1 )
458+ if var_name in variables :
459+ return value .replace (original , variables [var_name ]), ReplacementStatus .STANDARD_REPLACE
460+
461+ return "" , ReplacementStatus .NON_STANDARD_REPLACE
462+
463+
464+ def _replace_argument_variables (value : str , prev_value : str , variables : dict ) -> tuple [str , ReplacementStatus ]:
465+ """Replace variables in command-line arguments with values from variables dictionary.
466+
467+ Handles four argument formats:
468+ 1. Variable substitution: argument=${VAR_NAME}
469+ 2. Key-value pair: --argument=value
470+ 3. Space-separated pair: --argument value (where prev_value represents --argument)
471+
472+ Args:
473+ value: The current argument string that may contain variables
474+ prev_value: The previous argument string (for space-separated pairs)
475+ variables: Dictionary mapping variable names to their values
476+
477+ Returns:
478+ Tuple[str, bool]:
479+ String with all variables replaced with their values from the variables dict
480+ bool: whether the argument formatted as standard format in the ${} pattern
481+
482+ """
483+ if not isinstance (value , str ):
484+ return value , ReplacementStatus .NOT_REPLACED
428485
486+ # arg: VAR=${VAR}
429487 # check if the value contains a variable
430488 matched = re .search (r"\$\{([^}]+)\}" , value )
431489 if matched :
432490 original , var_name = matched .group (0 ), matched .group (1 )
433491 # Use empty string as default when key not found
434- return value .replace (original , variables .get (var_name , "" ))
435- return value
492+ return value .replace (original , variables .get (var_name , "" )), ReplacementStatus .STANDARD_REPLACE
493+
494+ # arg: --VAR=your var value
495+ key_value_match = re .match (r"^([A-Z_]+)=(.+)$" , value )
496+ if key_value_match :
497+ arg_key = key_value_match .group (1 )
498+ if arg_key in variables :
499+ # replace the arg_value with variables[arg_key]
500+ return f"{ arg_key } ={ variables [arg_key ]} " , ReplacementStatus .NON_STANDARD_REPLACE
501+ # if not contains the arg_key then just return the original value
502+ return value , ReplacementStatus .NOT_REPLACED
503+
504+ # arg: --VAR your_var_value
505+ if prev_value .startswith ("--" ) or prev_value .startswith ("-" ):
506+ arg_key = prev_value .lstrip ("-" )
507+ if arg_key in variables :
508+ # replace the value with variables[arg_key]
509+ return variables [arg_key ], ReplacementStatus .NON_STANDARD_REPLACE
510+ # if not contains the arg_key then just return the original value
511+ return value , ReplacementStatus .NOT_REPLACED
512+
513+ # nothing to replace
514+ return value , ReplacementStatus .NOT_REPLACED
0 commit comments