Skip to content

Commit f20e469

Browse files
calmininiechen
andauthored
fix: handle argument value replacement with more patterns (#75)
* fix: handle argument value replacement with more patterns * fix: add warning log --------- Co-authored-by: cnie <git@cnie.xyz>
1 parent f448cc9 commit f20e469

File tree

1 file changed

+86
-7
lines changed
  • src/mcpm/commands/server_operations

1 file changed

+86
-7
lines changed

src/mcpm/commands/server_operations/add.py

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import os
77
import re
8+
from enum import Enum
89

910
import click
1011
from 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

Comments
 (0)