diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs index 5824b2e054..0c8bec97ec 100644 --- a/src/Cli.Tests/ConfigureOptionsTests.cs +++ b/src/Cli.Tests/ConfigureOptionsTests.cs @@ -1081,6 +1081,96 @@ public void TestConfigureDescriptionForMcpSettings(string descriptionValue) Assert.AreEqual(descriptionValue, runtimeConfig.Runtime.Mcp.Description); } + /// + /// Tests that running "dab configure --runtime.mcp.dml-tools.{tool} {value}" updates + /// the individual DML tool boolean in the runtime config. Most tools are configured as + /// direct booleans in the schema (e.g., "describe-entities": true) rather than nested + /// objects with .enabled; "aggregate-records" additionally supports an object form + /// when specifying a query-timeout. + /// + [DataTestMethod] + [DataRow(true, DisplayName = "Enable individual DML tool: describe-entities")] + [DataRow(false, DisplayName = "Disable individual DML tool: describe-entities")] + public void TestConfigureIndividualDmlToolForMcpSettings(bool updatedValue) + { + // Arrange + SetupFileSystemWithInitialConfig(INITIAL_CONFIG); + + // Act: Set describe-entities via the corrected option name (no .enabled suffix) + ConfigureOptions options = new( + runtimeMcpDmlToolsDescribeEntitiesEnabled: updatedValue, + config: TEST_RUNTIME_CONFIG_FILE + ); + bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!); + + // Assert + Assert.IsTrue(isSuccess); + string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? runtimeConfig)); + Assert.IsNotNull(runtimeConfig.Runtime?.Mcp?.DmlTools); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.DescribeEntities); + } + + /// + /// Tests that running "dab configure --runtime.mcp.dml-tools.enabled {value}" sets all + /// DML tools at once via the bulk toggle. + /// + [DataTestMethod] + [DataRow(true, DisplayName = "Enable all DML tools at once")] + [DataRow(false, DisplayName = "Disable all DML tools at once")] + public void TestConfigureAllDmlToolsForMcpSettings(bool updatedValue) + { + // Arrange + SetupFileSystemWithInitialConfig(INITIAL_CONFIG); + + // Act: Set all tools via the bulk enabled toggle + ConfigureOptions options = new( + runtimeMcpDmlToolsEnabled: updatedValue, + config: TEST_RUNTIME_CONFIG_FILE + ); + bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!); + + // Assert + Assert.IsTrue(isSuccess); + string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? runtimeConfig)); + Assert.IsNotNull(runtimeConfig.Runtime?.Mcp?.DmlTools); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.DescribeEntities); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.CreateRecord); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.ReadRecords); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.UpdateRecord); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.DeleteRecord); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.ExecuteEntity); + Assert.AreEqual(updatedValue, runtimeConfig.Runtime.Mcp.DmlTools.AggregateRecords); + } + + /// + /// Tests that running "dab configure" with multiple individual DML tool options + /// correctly updates each tool independently. + /// + [TestMethod] + public void TestConfigureMultipleIndividualDmlToolsForMcpSettings() + { + // Arrange + SetupFileSystemWithInitialConfig(INITIAL_CONFIG); + + // Act: Enable describe-entities, disable create-record + ConfigureOptions options = new( + runtimeMcpDmlToolsDescribeEntitiesEnabled: true, + runtimeMcpDmlToolsCreateRecordEnabled: false, + config: TEST_RUNTIME_CONFIG_FILE + ); + bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!); + + // Assert + Assert.IsTrue(isSuccess); + string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? runtimeConfig)); + Assert.IsNotNull(runtimeConfig.Runtime?.Mcp?.DmlTools); + Assert.AreEqual(true, runtimeConfig.Runtime.Mcp.DmlTools.DescribeEntities); + Assert.AreEqual(false, runtimeConfig.Runtime.Mcp.DmlTools.CreateRecord); + } + /// /// Validates that `dab configure --show-effective-permissions` correctly displays /// effective permissions without modifying the config file. diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index 4ae27f790f..1620e3e588 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -1271,4 +1271,37 @@ public void TestUpdateDatabaseType(string dbType, bool isSuccess) // Assert Assert.AreEqual(isSuccess, isError == 0); } + + /// + /// End-to-end test verifying that the corrected CLI option names for individual + /// MCP DML tools (without the .enabled suffix) are correctly parsed by + /// CommandLineParser and produce the expected config output. + /// + [DataTestMethod] + [DataRow("--runtime.mcp.dml-tools.describe-entities", "true", DisplayName = "E2E: configure describe-entities via CLI")] + [DataRow("--runtime.mcp.dml-tools.create-record", "false", DisplayName = "E2E: configure create-record via CLI")] + [DataRow("--runtime.mcp.dml-tools.read-records", "true", DisplayName = "E2E: configure read-records via CLI")] + [DataRow("--runtime.mcp.dml-tools.update-record", "true", DisplayName = "E2E: configure update-record via CLI")] + [DataRow("--runtime.mcp.dml-tools.delete-record", "false", DisplayName = "E2E: configure delete-record via CLI")] + [DataRow("--runtime.mcp.dml-tools.execute-entity", "true", DisplayName = "E2E: configure execute-entity via CLI")] + [DataRow("--runtime.mcp.dml-tools.aggregate-records", "false", DisplayName = "E2E: configure aggregate-records via CLI")] + public void TestConfigureIndividualDmlToolViaCli(string optionName, string value) + { + // Initialize the config file. + string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type", + "mssql", "--connection-string", TEST_ENV_CONN_STRING }; + Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!); + + Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig)); + Assert.IsNotNull(runtimeConfig); + + // Act: Run configure with the individual DML tool option through the full CLI parsing path. + string[] runtimeArgs = { "configure", "-c", TEST_RUNTIME_CONFIG_FILE, optionName, value }; + int exitCode = Program.Execute(runtimeArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!); + + // Assert: Command succeeds and the config contains MCP DML tools section. + Assert.AreEqual(0, exitCode); + Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? updatedConfig)); + Assert.IsNotNull(updatedConfig?.Runtime?.Mcp?.DmlTools); + } } diff --git a/src/Cli/Commands/ConfigureOptions.cs b/src/Cli/Commands/ConfigureOptions.cs index be076d7983..970c09d052 100644 --- a/src/Cli/Commands/ConfigureOptions.cs +++ b/src/Cli/Commands/ConfigureOptions.cs @@ -212,25 +212,25 @@ public ConfigureOptions( [Option("runtime.mcp.dml-tools.enabled", Required = false, HelpText = "Enable DAB's MCP DML tools endpoint. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsEnabled { get; } - [Option("runtime.mcp.dml-tools.describe-entities.enabled", Required = false, HelpText = "Enable DAB's MCP describe entities tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.describe-entities", Required = false, HelpText = "Enable DAB's MCP describe entities tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsDescribeEntitiesEnabled { get; } - [Option("runtime.mcp.dml-tools.create-record.enabled", Required = false, HelpText = "Enable DAB's MCP create record tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.create-record", Required = false, HelpText = "Enable DAB's MCP create record tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsCreateRecordEnabled { get; } - [Option("runtime.mcp.dml-tools.read-records.enabled", Required = false, HelpText = "Enable DAB's MCP read record tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.read-records", Required = false, HelpText = "Enable DAB's MCP read record tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsReadRecordsEnabled { get; } - [Option("runtime.mcp.dml-tools.update-record.enabled", Required = false, HelpText = "Enable DAB's MCP update record tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.update-record", Required = false, HelpText = "Enable DAB's MCP update record tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsUpdateRecordEnabled { get; } - [Option("runtime.mcp.dml-tools.delete-record.enabled", Required = false, HelpText = "Enable DAB's MCP delete record tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.delete-record", Required = false, HelpText = "Enable DAB's MCP delete record tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsDeleteRecordEnabled { get; } - [Option("runtime.mcp.dml-tools.execute-entity.enabled", Required = false, HelpText = "Enable DAB's MCP execute entity tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.execute-entity", Required = false, HelpText = "Enable DAB's MCP execute entity tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsExecuteEntityEnabled { get; } - [Option("runtime.mcp.dml-tools.aggregate-records.enabled", Required = false, HelpText = "Enable DAB's MCP aggregate records tool. Default: true (boolean).")] + [Option("runtime.mcp.dml-tools.aggregate-records", Required = false, HelpText = "Enable DAB's MCP aggregate records tool. Default: true (boolean).")] public bool? RuntimeMcpDmlToolsAggregateRecordsEnabled { get; } [Option("runtime.mcp.dml-tools.aggregate-records.query-timeout", Required = false, HelpText = "Set the execution timeout in seconds for the aggregate-records MCP tool. Default: 30 (integer). Range: 1-600.")]