From 013deb663a123afa04562f02d4c2a6946c34f4b6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 10 Oct 2025 04:23:54 +0200 Subject: [PATCH 1/4] Add invoke_dsc_config() MCP tool --- dsc/locales/en-us.toml | 7 + dsc/src/mcp/invoke_dsc_config.rs | 216 ++++++++++++++++++++++++++ dsc/src/mcp/mcp_server.rs | 3 +- dsc/src/mcp/mod.rs | 1 + dsc/tests/dsc_mcp.tests.ps1 | 252 +++++++++++++++++++++++++++++++ 5 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 dsc/src/mcp/invoke_dsc_config.rs diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index f1e47d6ec..661cbcc53 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -66,6 +66,13 @@ serverStopped = "MCP server stopped" failedToCreateRuntime = "Failed to create async runtime: %{error}" serverWaitFailed = "Failed to wait for MCP server: %{error}" +[mcp.invoke_dsc_config] +invalidConfiguration = "Invalid configuration document" +invalidParameters = "Invalid parameters" +failedConvertJson = "Failed to convert to JSON" +failedSerialize = "Failed to serialize configuration" +failedSetParameters = "Failed to set parameters" + [mcp.invoke_dsc_resource] resourceNotFound = "Resource type '%{resource}' does not exist" diff --git a/dsc/src/mcp/invoke_dsc_config.rs b/dsc/src/mcp/invoke_dsc_config.rs new file mode 100644 index 000000000..9b0882b94 --- /dev/null +++ b/dsc/src/mcp/invoke_dsc_config.rs @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::mcp::mcp_server::McpServer; +use dsc_lib::{ + configure::{ + config_doc::Configuration, + config_result::{ + ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult, + ConfigurationTestResult, + }, + Configurator, + }, + progress::ProgressFormat, +}; +use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json}; +use rust_i18n::t; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use tokio::task; + +#[derive(Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ConfigOperation { + Get, + Set, + Test, + Export, +} + +#[derive(Serialize, JsonSchema)] +#[serde(untagged)] +pub enum ConfigOperationResult { + GetResult(Box), + SetResult(Box), + TestResult(Box), + ExportResult(Box), +} + +#[derive(Serialize, JsonSchema)] +pub struct InvokeDscConfigResponse { + pub result: ConfigOperationResult, +} + +#[derive(Deserialize, JsonSchema)] +pub struct InvokeDscConfigRequest { + #[schemars(description = "The operation to perform on the DSC configuration")] + pub operation: ConfigOperation, + #[schemars(description = "The DSC configuration document as JSON or YAML string")] + pub configuration: String, + #[schemars( + description = "Optional parameters to pass to the configuration as JSON or YAML string" + )] + pub parameters: Option, +} + +#[tool_router(router = invoke_dsc_config_router, vis = "pub")] +impl McpServer { + #[tool( + description = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters", + annotations( + title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters", + read_only_hint = false, + destructive_hint = true, + idempotent_hint = true, + open_world_hint = true, + ) + )] + pub async fn invoke_dsc_config( + &self, + Parameters(InvokeDscConfigRequest { + operation, + configuration, + parameters, + }): Parameters, + ) -> Result, McpError> { + let result = task::spawn_blocking(move || { + let config: Configuration = match serde_json::from_str(&configuration) { + Ok(config) => config, + Err(_) => { + match serde_yaml::from_str::(&configuration) { + Ok(yaml_value) => match serde_json::to_value(yaml_value) { + Ok(json_value) => match serde_json::from_value(json_value) { + Ok(config) => config, + Err(e) => { + return Err(McpError::invalid_request( + format!( + "{}: {e}", + t!("mcp.invoke_dsc_config.invalidConfiguration") + ), + None, + )) + } + }, + Err(e) => { + return Err(McpError::invalid_request( + format!( + "{}: {e}", + t!("mcp.invoke_dsc_config.failedConvertJson") + ), + None, + )) + } + }, + Err(e) => { + return Err(McpError::invalid_request( + format!( + "{}: {e}", + t!("mcp.invoke_dsc_config.invalidConfiguration") + ), + None, + )) + } + } + } + }; + + let config_json = match serde_json::to_string(&config) { + Ok(json) => json, + Err(e) => { + return Err(McpError::internal_error( + format!("{}: {e}", t!("mcp.invoke_dsc_config.failedSerialize")), + None, + )) + } + }; + + let mut configurator = match Configurator::new(&config_json, ProgressFormat::None) { + Ok(configurator) => configurator, + Err(e) => return Err(McpError::internal_error(e.to_string(), None)), + }; + + configurator.context.dsc_version = Some(env!("CARGO_PKG_VERSION").to_string()); + + let parameters_value: Option = if let Some(params_str) = parameters { + let params_json = match serde_json::from_str(¶ms_str) { + Ok(json) => json, + Err(_) => { + match serde_yaml::from_str::(¶ms_str) { + Ok(yaml) => match serde_json::to_value(yaml) { + Ok(json) => json, + Err(e) => { + return Err(McpError::invalid_request( + format!( + "{}: {e}", + t!("mcp.invoke_dsc_config.failedConvertJson") + ), + None, + )) + } + }, + Err(e) => { + return Err(McpError::invalid_request( + format!( + "{}: {e}", + t!("mcp.invoke_dsc_config.invalidParameters") + ), + None, + )) + } + } + } + }; + + // Wrap parameters in a "parameters" field for configurator.set_context() + Some(serde_json::json!({ + "parameters": params_json + })) + } else { + None + }; + + if let Err(e) = configurator.set_context(parameters_value.as_ref()) { + return Err(McpError::invalid_request( + format!("{}: {e}", t!("mcp.invoke_dsc_config.failedSetParameters")), + None, + )); + } + + match operation { + ConfigOperation::Get => { + let result = match configurator.invoke_get() { + Ok(res) => res, + Err(e) => return Err(McpError::internal_error(e.to_string(), None)), + }; + Ok(ConfigOperationResult::GetResult(Box::new(result))) + } + ConfigOperation::Set => { + let result = match configurator.invoke_set(false) { + Ok(res) => res, + Err(e) => return Err(McpError::internal_error(e.to_string(), None)), + }; + Ok(ConfigOperationResult::SetResult(Box::new(result))) + } + ConfigOperation::Test => { + let result = match configurator.invoke_test() { + Ok(res) => res, + Err(e) => return Err(McpError::internal_error(e.to_string(), None)), + }; + Ok(ConfigOperationResult::TestResult(Box::new(result))) + } + ConfigOperation::Export => { + let result = match configurator.invoke_export() { + Ok(res) => res, + Err(e) => return Err(McpError::internal_error(e.to_string(), None)), + }; + Ok(ConfigOperationResult::ExportResult(Box::new(result))) + } + } + }) + .await + .map_err(|e| McpError::internal_error(e.to_string(), None))??; + + Ok(Json(InvokeDscConfigResponse { result })) + } +} diff --git a/dsc/src/mcp/mcp_server.rs b/dsc/src/mcp/mcp_server.rs index 6f75af3a8..748355922 100644 --- a/dsc/src/mcp/mcp_server.rs +++ b/dsc/src/mcp/mcp_server.rs @@ -21,7 +21,8 @@ impl McpServer { pub fn new() -> Self { Self { tool_router: - Self::invoke_dsc_resource_router() + Self::invoke_dsc_config_router() + + Self::invoke_dsc_resource_router() + Self::list_dsc_functions_router() + Self::list_dsc_resources_router() + Self::show_dsc_resource_router() diff --git a/dsc/src/mcp/mod.rs b/dsc/src/mcp/mod.rs index 3cf1e3a8e..51c0084bf 100644 --- a/dsc/src/mcp/mod.rs +++ b/dsc/src/mcp/mod.rs @@ -9,6 +9,7 @@ use rmcp::{ }; use rust_i18n::t; +pub mod invoke_dsc_config; pub mod invoke_dsc_resource; pub mod list_dsc_functions; pub mod list_dsc_resources; diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index f7c52ffea..32a9260fd 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -71,6 +71,7 @@ Describe 'Tests for MCP server' { } $tools = @{ + 'invoke_dsc_config' = $false 'invoke_dsc_resource' = $false 'list_dsc_functions' = $false 'list_dsc_resources' = $false @@ -333,4 +334,255 @@ Describe 'Tests for MCP server' { $response.result.structuredContent.result.$property.action | Should -BeExactly $operation -Because $because $response.result.structuredContent.result.$property.hello | Should -BeExactly "World" -Because $because } + + It 'Calling invoke_dsc_config for operation: ' -TestCases @( + @{ operation = 'get' } + @{ operation = 'set' } + @{ operation = 'test' } + ) { + param($operation) + + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + resources = @( + @{ + name = 'TestOperation' + type = 'Test/Operation' + properties = @{ + hello = 'Hello from config' + action = $operation + } + } + ) + } + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 13 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = $operation + configuration = ($config | ConvertTo-Json -Depth 20) + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 13 + $because = ($response | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.result.results | Should -Not -BeNullOrEmpty -Because $because + $response.result.structuredContent.result.results.Count | Should -Be 1 -Because $because + $response.result.structuredContent.result.results[0].name | Should -Be 'TestOperation' -Because $because + } + + It 'Calling invoke_dsc_config for export operation' { + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + resources = @( + @{ + name = 'TestExport' + type = 'Test/Export' + properties = @{ + count = 2 + } + } + ) + } + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 13 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'export' + configuration = ($config | ConvertTo-Json -Depth 20) + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 13 + $because = ($response | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.result.result | Should -Not -BeNullOrEmpty -Because $because + $response.result.structuredContent.result.result.resources.Count | Should -Be 2 -Because $because + $response.result.structuredContent.result.result.resources[0].name | Should -Be 'TestName' -Because $because + } + + It 'Calling invoke_dsc_config with parameters works' { + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + parameters = @{ + message = @{ + type = 'string' + defaultValue = 'default message' + } + } + resources = @( + @{ + name = 'TestResource' + type = 'Test/Operation' + properties = @{ + hello = "[parameters('message')]" + action = 'get' + } + } + ) + } + + $parameters = @{ + message = 'custom message' + } + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 14 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'get' + configuration = ($config | ConvertTo-Json -Depth 20) + parameters = ($parameters | ConvertTo-Json -Depth 20) + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 14 + $because = ($response | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.result.results[0].result.actualState.hello | Should -Be 'custom message' -Because $because + } + + It 'Calling invoke_dsc_config with YAML configuration works' { + $configYaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: TestResource + type: Test/Operation + properties: + hello: Hello from YAML + action: get +'@ + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 15 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'get' + configuration = $configYaml + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 15 + $because = ($response | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.result.results[0].result.actualState.hello | Should -Be 'Hello from YAML' -Because $because + } + + It 'Calling invoke_dsc_config with YAML parameters works' { + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + parameters = @{ + greeting = @{ + type = 'string' + } + } + resources = @( + @{ + name = 'TestResource' + type = 'Test/Operation' + properties = @{ + hello = "[parameters('greeting')]" + action = 'get' + } + } + ) + } + + $parametersYaml = @' +greeting: Hello from YAML parameters +'@ + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 16 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'get' + configuration = ($config | ConvertTo-Json -Depth 20) + parameters = $parametersYaml + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 16 + $because = ($response | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.result.results[0].result.actualState.hello | Should -Be 'Hello from YAML parameters' -Because $because + } + + It 'Calling invoke_dsc_config with invalid configuration returns error' { + $mcpRequest = @{ + jsonrpc = "2.0" + id = 17 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'get' + configuration = '{ invalid json }' + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 17 + $response.error.code | Should -Be -32600 + $response.error.message | Should -Match 'Invalid configuration' + } + + It 'Calling invoke_dsc_config with invalid parameters returns error' { + $config = @{ + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + resources = @( + @{ + name = 'TestResource' + type = 'Test/Operation' + properties = @{ + hello = 'test' + action = 'get' + } + } + ) + } + + $mcpRequest = @{ + jsonrpc = "2.0" + id = 18 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" + arguments = @{ + operation = 'get' + configuration = ($config | ConvertTo-Json -Depth 20) + parameters = '{[invalid' + } + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 18 + $response.error.code | Should -Be -32600 + $response.error.message | Should -Match 'Invalid parameters' + } } From 4b7c82ada5f7dc0c7a400917141a2b4f85a6e428 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 12 Oct 2025 08:33:29 +0200 Subject: [PATCH 2/4] Format --- dsc/tests/dsc_mcp.tests.ps1 | 256 ++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index b7bf5d20d..8c9d819fc 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -34,15 +34,15 @@ Describe 'Tests for MCP server' { It 'Initialization works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 1 - method = "initialize" - params = @{ + id = 1 + method = "initialize" + params = @{ protocolVersion = "2024-11-05" - capabilities = @{ + capabilities = @{ tools = @{} } - clientInfo = @{ - name = "Test Client" + clientInfo = @{ + name = "Test Client" version = "1.0.0" } } @@ -56,7 +56,7 @@ Describe 'Tests for MCP server' { $notifyInitialized = @{ jsonrpc = "2.0" - method = "notifications/initialized" + method = "notifications/initialized" } Send-McpRequest -request $notifyInitialized -notify @@ -65,16 +65,16 @@ Describe 'Tests for MCP server' { It 'Tools/List works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 2 - method = "tools/list" - params = @{} + id = 2 + method = "tools/list" + params = @{} } $tools = @{ 'invoke_dsc_resource' = $false - 'list_dsc_functions' = $false - 'list_dsc_resources' = $false - 'show_dsc_resource' = $false + 'list_dsc_functions' = $false + 'list_dsc_resources' = $false + 'show_dsc_resource' = $false } $response = Send-McpRequest -request $mcpRequest @@ -93,10 +93,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_resources works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 3 - method = "tools/call" - params = @{ - name = "list_dsc_resources" + id = 3 + method = "tools/call" + params = @{ + name = "list_dsc_resources" arguments = @{} } } @@ -116,10 +116,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_resources with adapter works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 4 - method = "tools/call" - params = @{ - name = "list_dsc_resources" + id = 4 + method = "tools/call" + params = @{ + name = "list_dsc_resources" arguments = @{ adapter = "Microsoft.DSC/PowerShell" } @@ -139,17 +139,17 @@ Describe 'Tests for MCP server' { } It 'Calling list_dsc_resources with returns error' -TestCases @( - @{"adapter" = "Non.Existent/Adapter"}, - @{"adapter" = "Microsoft.DSC.Debug/Echo"} + @{"adapter" = "Non.Existent/Adapter" }, + @{"adapter" = "Microsoft.DSC.Debug/Echo" } ) { param($adapter) $mcpRequest = @{ jsonrpc = "2.0" - id = 5 - method = "tools/call" - params = @{ - name = "list_dsc_resources" + id = 5 + method = "tools/call" + params = @{ + name = "list_dsc_resources" arguments = @{ adapter = $adapter } @@ -167,10 +167,10 @@ Describe 'Tests for MCP server' { $mcpRequest = @{ jsonrpc = "2.0" - id = 6 - method = "tools/call" - params = @{ - name = "show_dsc_resource" + id = 6 + method = "tools/call" + params = @{ + name = "show_dsc_resource" arguments = @{ type = $resource.type } @@ -195,10 +195,10 @@ Describe 'Tests for MCP server' { It 'Calling show_dsc_resource with non-existent resource returns error' { $mcpRequest = @{ jsonrpc = "2.0" - id = 7 - method = "tools/call" - params = @{ - name = "show_dsc_resource" + id = 7 + method = "tools/call" + params = @{ + name = "show_dsc_resource" arguments = @{ type = "Non.Existent/Resource" } @@ -214,10 +214,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_functions works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 8 - method = "tools/call" - params = @{ - name = "list_dsc_functions" + id = 8 + method = "tools/call" + params = @{ + name = "list_dsc_functions" arguments = @{} } } @@ -241,10 +241,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_functions with function_filter filter works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 9 - method = "tools/call" - params = @{ - name = "list_dsc_functions" + id = 9 + method = "tools/call" + params = @{ + name = "list_dsc_functions" arguments = @{ function_filter = "array" } @@ -261,10 +261,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_functions with wildcard pattern works' { $mcpRequest = @{ jsonrpc = "2.0" - id = 10 - method = "tools/call" - params = @{ - name = "list_dsc_functions" + id = 10 + method = "tools/call" + params = @{ + name = "list_dsc_functions" arguments = @{ function_filter = "*Array*" } @@ -284,10 +284,10 @@ Describe 'Tests for MCP server' { It 'Calling list_dsc_functions with invalid pattern returns empty result' { $mcpRequest = @{ jsonrpc = "2.0" - id = 11 - method = "tools/call" - params = @{ - name = "list_dsc_functions" + id = 11 + method = "tools/call" + params = @{ + name = "list_dsc_functions" arguments = @{ function_filter = "[invalid]" } @@ -310,18 +310,18 @@ Describe 'Tests for MCP server' { $mcpRequest = @{ jsonrpc = "2.0" - id = 12 - method = "tools/call" - params = @{ - name = "invoke_dsc_resource" + id = 12 + method = "tools/call" + params = @{ + name = "invoke_dsc_resource" arguments = @{ - type = 'Test/Operation' - operation = $operation - resource_type = 'Test/Operation' + type = 'Test/Operation' + operation = $operation + resource_type = 'Test/Operation' properties_json = (@{ - hello = "World" - action = $operation - } | ConvertTo-Json -Depth 20) + hello = "World" + action = $operation + } | ConvertTo-Json -Depth 20) } } } @@ -337,18 +337,18 @@ Describe 'Tests for MCP server' { It 'Calling invoke_dsc_resource for delete operation' { $mcpRequest = @{ jsonrpc = "2.0" - id = 12 - method = "tools/call" - params = @{ - name = "invoke_dsc_resource" + id = 12 + method = "tools/call" + params = @{ + name = "invoke_dsc_resource" arguments = @{ - type = 'Test/Operation' - operation = 'delete' - resource_type = 'Test/Operation' + type = 'Test/Operation' + operation = 'delete' + resource_type = 'Test/Operation' properties_json = (@{ - hello = "World" - action = 'delete' - } | ConvertTo-Json -Depth 20) + hello = "World" + action = 'delete' + } | ConvertTo-Json -Depth 20) } } } @@ -371,10 +371,10 @@ Describe 'Tests for MCP server' { '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' resources = @( @{ - name = 'TestOperation' - type = 'Test/Operation' + name = 'TestOperation' + type = 'Test/Operation' properties = @{ - hello = 'Hello from config' + hello = 'Hello from config' action = $operation } } @@ -383,12 +383,12 @@ Describe 'Tests for MCP server' { $mcpRequest = @{ jsonrpc = "2.0" - id = 13 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 13 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = $operation + operation = $operation configuration = ($config | ConvertTo-Json -Depth 20) } } @@ -407,8 +407,8 @@ Describe 'Tests for MCP server' { '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' resources = @( @{ - name = 'TestExport' - type = 'Test/Export' + name = 'TestExport' + type = 'Test/Export' properties = @{ count = 2 } @@ -418,12 +418,12 @@ Describe 'Tests for MCP server' { $mcpRequest = @{ jsonrpc = "2.0" - id = 13 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 13 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'export' + operation = 'export' configuration = ($config | ConvertTo-Json -Depth 20) } } @@ -439,19 +439,19 @@ Describe 'Tests for MCP server' { It 'Calling invoke_dsc_config with parameters works' { $config = @{ - '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' parameters = @{ message = @{ - type = 'string' + type = 'string' defaultValue = 'default message' } } - resources = @( + resources = @( @{ - name = 'TestResource' - type = 'Test/Operation' + name = 'TestResource' + type = 'Test/Operation' properties = @{ - hello = "[parameters('message')]" + hello = "[parameters('message')]" action = 'get' } } @@ -464,14 +464,14 @@ Describe 'Tests for MCP server' { $mcpRequest = @{ jsonrpc = "2.0" - id = 14 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 14 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'get' + operation = 'get' configuration = ($config | ConvertTo-Json -Depth 20) - parameters = ($parameters | ConvertTo-Json -Depth 20) + parameters = ($parameters | ConvertTo-Json -Depth 20) } } } @@ -495,12 +495,12 @@ resources: $mcpRequest = @{ jsonrpc = "2.0" - id = 15 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 15 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'get' + operation = 'get' configuration = $configYaml } } @@ -514,18 +514,18 @@ resources: It 'Calling invoke_dsc_config with YAML parameters works' { $config = @{ - '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' parameters = @{ greeting = @{ type = 'string' } } - resources = @( + resources = @( @{ - name = 'TestResource' - type = 'Test/Operation' + name = 'TestResource' + type = 'Test/Operation' properties = @{ - hello = "[parameters('greeting')]" + hello = "[parameters('greeting')]" action = 'get' } } @@ -538,14 +538,14 @@ greeting: Hello from YAML parameters $mcpRequest = @{ jsonrpc = "2.0" - id = 16 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 16 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'get' + operation = 'get' configuration = ($config | ConvertTo-Json -Depth 20) - parameters = $parametersYaml + parameters = $parametersYaml } } } @@ -559,12 +559,12 @@ greeting: Hello from YAML parameters It 'Calling invoke_dsc_config with invalid configuration returns error' { $mcpRequest = @{ jsonrpc = "2.0" - id = 17 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 17 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'get' + operation = 'get' configuration = '{ invalid json }' } } @@ -581,10 +581,10 @@ greeting: Hello from YAML parameters '$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' resources = @( @{ - name = 'TestResource' - type = 'Test/Operation' + name = 'TestResource' + type = 'Test/Operation' properties = @{ - hello = 'test' + hello = 'test' action = 'get' } } @@ -593,14 +593,14 @@ greeting: Hello from YAML parameters $mcpRequest = @{ jsonrpc = "2.0" - id = 18 - method = "tools/call" - params = @{ - name = "invoke_dsc_config" + id = 18 + method = "tools/call" + params = @{ + name = "invoke_dsc_config" arguments = @{ - operation = 'get' + operation = 'get' configuration = ($config | ConvertTo-Json -Depth 20) - parameters = '{[invalid' + parameters = '{[invalid' } } } From 75871a121725c02ac420c271a5e493f946684d0c Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 12 Oct 2025 08:34:08 +0200 Subject: [PATCH 3/4] Add line break --- dsc/tests/dsc_mcp.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index 8c9d819fc..98fd540ea 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -610,4 +610,4 @@ greeting: Hello from YAML parameters $response.error.code | Should -Be -32600 $response.error.message | Should -Match 'Invalid parameters' } -} \ No newline at end of file +} From 0277279a25e4b4763db943daa52ed196ea3b848e Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 12 Oct 2025 10:27:11 +0200 Subject: [PATCH 4/4] Forgot tool count --- dsc/tests/dsc_mcp.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index 98fd540ea..336dcea3b 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -71,6 +71,7 @@ Describe 'Tests for MCP server' { } $tools = @{ + 'invoke_dsc_config' = $false 'invoke_dsc_resource' = $false 'list_dsc_functions' = $false 'list_dsc_resources' = $false