diff --git a/src/itential_mcp/runtime/constants.py b/src/itential_mcp/runtime/constants.py index 89695afe..7ee2540d 100644 --- a/src/itential_mcp/runtime/constants.py +++ b/src/itential_mcp/runtime/constants.py @@ -58,6 +58,7 @@ class CommandConfig: "metavar": "", "help": "Parameters to pass to the tool", }, + "--config": {"help": CONFIG_HELP_MESSAGE}, }, add_platform_group=True, ), diff --git a/src/itential_mcp/runtime/parser.py b/src/itential_mcp/runtime/parser.py index 8e350271..6598684a 100644 --- a/src/itential_mcp/runtime/parser.py +++ b/src/itential_mcp/runtime/parser.py @@ -146,8 +146,23 @@ def parse_args(args: Sequence) -> Tuple[Callable, Tuple[Any, ...], dict]: parser = _create_main_parser() _create_subparsers(parser) + # Pre-capture --config from raw args before argparse parsing. + # When --config appears before the subcommand (e.g., itential-mcp --config + # file.conf call ...), the main parser captures it but the subparser + # overwrites it with its default (None). This preserves the value. + pre_config = None + args_list = list(args) + if "--config" in args_list: + idx = args_list.index("--config") + if idx + 1 < len(args_list): + pre_config = args_list[idx + 1] + parsed_args = parser.parse_args(args=args) + # Restore --config if the subparser overwrote it with None + if parsed_args.config is None and pre_config is not None: + parsed_args.config = pre_config + _process_logging_config(parsed_args) if parsed_args.help or parsed_args.command is None: diff --git a/tests/test_commands.py b/tests/test_commands.py index cec7ae18..105f702a 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -371,6 +371,81 @@ def __init__(self, tool, params): assert result[1] == ("my_tool", '{"test": true}') + def test_call_with_config_attribute(self): + """Test that call function works when args has config attribute""" + + class MockArgs: + def __init__(self, tool, params, config): + self.tool = tool + self.params = params + self.config = config + + args = MockArgs("my_tool", '{"test": true}', "/path/to/config.conf") + result = commands.call(args) + + # call command should still return the same tuple structure + assert result[0] == runner.run + assert result[1] == ("my_tool", '{"test": true}') + assert result[2] is None + + +class TestCallCommandConfigIntegration: + """Test cases for call command --config integration""" + + def test_call_subparser_accepts_config_flag(self): + """Test that the call subparser accepts --config flag""" + from unittest.mock import patch + from itential_mcp.runtime.parser import _create_main_parser, _create_subparsers + + parser = _create_main_parser() + with patch("itential_mcp.runtime.parser.add_platform_group"), \ + patch("itential_mcp.runtime.parser.add_server_group"): + _create_subparsers(parser) + + # Should parse successfully with --config on call subcommand + args = parser.parse_args(["call", "get_health", "--config", "/path/to/config.conf"]) + + assert args.command == "call" + assert args.tool == "get_health" + assert args.config == "/path/to/config.conf" + + def test_call_subparser_config_default_is_none(self): + """Test that --config defaults to None when not provided""" + from unittest.mock import patch + from itential_mcp.runtime.parser import _create_main_parser, _create_subparsers + + parser = _create_main_parser() + with patch("itential_mcp.runtime.parser.add_platform_group"), \ + patch("itential_mcp.runtime.parser.add_server_group"): + _create_subparsers(parser) + + args = parser.parse_args(["call", "get_health"]) + + assert args.command == "call" + assert args.tool == "get_health" + assert args.config is None + + def test_call_subparser_config_with_params(self): + """Test that --config works alongside --params on call subcommand""" + from unittest.mock import patch + from itential_mcp.runtime.parser import _create_main_parser, _create_subparsers + + parser = _create_main_parser() + with patch("itential_mcp.runtime.parser.add_platform_group"), \ + patch("itential_mcp.runtime.parser.add_server_group"): + _create_subparsers(parser) + + args = parser.parse_args([ + "call", "get_devices", + "--config", "/path/to/config.conf", + "--params", '{"filter": "active"}', + ]) + + assert args.command == "call" + assert args.tool == "get_devices" + assert args.config == "/path/to/config.conf" + assert args.params == '{"filter": "active"}' + class TestModuleStructure: """Test cases for overall module structure and imports"""