Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ changes since the last release, see the [diff on GitHub][unreleased].
for the `v3.0.0.0-alpha.5` release. Leave the release links under the release section.
-->

### Added

- Added what-if support to the MCP server tools. The `invoke_dsc_config` tool now accepts a
`what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if`
for the `set` and `delete` operations. This mirrors the `--what-if` flag on the `dsc config set`,
`dsc resource set`, and `dsc resource delete` commands, enabling AI agents to preview changes
before applying them. Passing `what_if` with an operation that doesn't support it returns an
invalid parameters error.

## [v3.2.2][release-v3.2.2] - 2026-06-16

This section includes a summary of changes for the `3.2.2` release. For the full list of changes
Expand Down
29 changes: 25 additions & 4 deletions docs/concepts/dsc-mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ This enhances the overall authoring experience by providing contextual informati
local DSC environment directly to AI-powered tools.

> [!IMPORTANT]
> The DSC MCP server is focused on discovery and information retrieval. It does not
> directly perform any configuration changes or resource modifications unless requested to do so.
> The information it provides to AI agents can be used to generate configurations and commands
> that, when executed, will impact your system. Always review and validate any generated
> The DSC MCP server is primarily focused on discovery and information retrieval. It only
> performs configuration changes or resource modifications when an agent explicitly invokes the
> `invoke_dsc_config` or `invoke_dsc_resource` tools with the `set` or `delete` operation. Those
> tools support a `what_if` option so agents can preview a change before applying it. The
> information the server provides to AI agents can be used to generate configurations and
> commands that, when executed, will impact your system. Always review and validate any generated
> content before execution.

## What is Model Context Protocol (MCP)?
Expand Down Expand Up @@ -74,6 +76,24 @@ to help solve your specific needs:

This helps agents suggest the most suitable approach using your available DSC capabilities.

### Previewing changes with what-if

Before an agent applies a configuration or resource change, it can simulate the change to show
you what would happen without modifying your system. The `invoke_dsc_config` tool accepts a
`what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if`
for the `set` and `delete` operations. This is the same behavior as the `--what-if` flag on the
`dsc config set`, `dsc resource set`, and `dsc resource delete` commands:

- **You ask**: "Show me what would change if I applied this configuration"
- **Agent invokes**: `invoke_dsc_config` with `operation: set` and `what_if: true`
- **Agent provides**: The projected before and after state for each resource, with the result
metadata reporting `executionType` as `whatIf`

Resources that natively support what-if run their simulation directly. For resources that don't,
DSC generates a synthetic what-if result from the resource's `test` operation. When `what_if` is
requested with an operation that doesn't support it, such as `get`, the tool returns an error
instead of silently ignoring the option.

> [!NOTE]
> Additional MCP tools will become available in future releases to expand the capabilities
> of the DSC MCP server integration. For the latest updates and feature announcements,
Expand Down Expand Up @@ -194,6 +214,7 @@ Example prompts that work well with DSC MCP integration:
- "What DSC resources are available on this machine?"
- "Show me the schema for the Microsoft.Windows/Registry resource"
- "List all available DSC functions I can use in expressions"
- "Preview what this configuration would change before applying it"

:::image type="complex" source="media/dsc-mcp-server/dsc-mcp-usage-example.png" alt-text="Screenshot showing DSC MCP usage example in VS Code":::
This screenshot demonstrates the DSC MCP integration in action, showing how AI agents use the MCP tools to provide contextual assistance with DSC-related tasks in VS Code.
Expand Down
2 changes: 2 additions & 0 deletions dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ invalidParameters = "Invalid parameters"
failedConvertJson = "Failed to convert to JSON"
failedSerialize = "Failed to serialize configuration"
failedSetParameters = "Failed to set parameters"
whatIfOnlySet = "what_if is only supported for the 'set' operation"

[server.invoke_dsc_expression]
parserInitializationFailed = "Failed to initialize parser: %{error}"
Expand All @@ -113,6 +114,7 @@ functionInvocationFailed = "Function '%{function}' invocation failed: %{error}"

[server.invoke_dsc_resource]
resourceNotFound = "Resource type '%{resource}' does not exist"
whatIfNotSupported = "what_if is only supported for the 'set' and 'delete' operations"

[server.list_dsc_functions]
invalidNamePattern = "Invalid function name pattern '%{pattern}'"
Expand Down
22 changes: 19 additions & 3 deletions dsc/src/server/invoke_dsc_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::server::mcp_server::McpServer;
use dsc_lib::{
configure::{
config_doc::Configuration,
config_doc::{Configuration, ExecutionKind},
config_result::{
ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult,
ConfigurationTestResult,
Expand Down Expand Up @@ -52,14 +52,19 @@ pub struct InvokeDscConfigRequest {
description = "Optional parameters to pass to the configuration as a YAML string"
)]
pub parameters: Option<String>,
#[schemars(
description = "When true and operation is 'set', simulate the change (what-if / dry-run) instead of applying it. The result includes 'metadata.Microsoft.DSC.executionType' = 'whatIf'. Only valid with the 'set' operation."
)]
#[serde(default)]
pub what_if: Option<bool>,
}

#[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",
description = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters. Set 'what_if' to true to preview a Set without applying changes.",
annotations(
title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters",
title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters and what-if support",
read_only_hint = false,
destructive_hint = true,
idempotent_hint = true,
Expand All @@ -72,6 +77,7 @@ impl McpServer {
operation,
configuration,
parameters,
what_if,
}): Parameters<InvokeDscConfigRequest>,
) -> Result<Json<InvokeDscConfigResponse>, McpError> {
let result = task::spawn_blocking(move || {
Expand Down Expand Up @@ -127,6 +133,16 @@ impl McpServer {

configurator.context.dsc_version = Some(env!("CARGO_PKG_VERSION").to_string());

if what_if.unwrap_or(false) {
if !matches!(operation, ConfigOperation::Set) {
return Err(McpError::invalid_params(
t!("server.invoke_dsc_config.whatIfOnlySet"),
None,
));
}
configurator.context.execution_type = ExecutionKind::WhatIf;
}

let parameters_value: Option<serde_json::Value> = if let Some(params_str) = parameters {
let params_json = match serde_yaml::from_str::<serde_yaml::Value>(&params_str) {
Ok(yaml) => match serde_json::to_value(yaml) {
Expand Down
28 changes: 22 additions & 6 deletions dsc/src/server/invoke_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use dsc_lib::{
dscresources::{
dscresource::Invoke,
invoke_result::{
DeleteResult,
DeleteResultKind,
ExportResult,
GetResult,
SetResult,
Expand Down Expand Up @@ -39,6 +41,7 @@ pub enum ResourceOperationResult {
TestResult(TestResult),
ExportResult(ExportResult),
DeleteResult { success: bool },
DeleteWhatIfResult(DeleteResult),
}

#[derive(Serialize, JsonSchema)]
Expand All @@ -54,22 +57,33 @@ pub struct InvokeDscResourceRequest {
pub resource_type: FullyQualifiedTypeName,
#[schemars(description = "The properties to pass to the DSC resource as JSON. Must match the resource JSON schema from `show_dsc_resource` tool.")]
pub properties_json: String,
#[schemars(description = "When true and operation is 'set' or 'delete', simulate the change (what-if / dry-run) instead of applying it. Resources without native what-if support return a synthetic result derived from 'test'. Only valid with the 'set' and 'delete' operations.")]
#[serde(default)]
pub what_if: Option<bool>,
}

#[tool_router(router = invoke_dsc_resource_router, vis = "pub")]
impl McpServer {
#[tool(
description = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format",
description = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format. Set 'what_if' to true to preview a Set or Delete without applying changes.",
annotations(
title = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format",
title = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format and what-if support",
read_only_hint = false,
destructive_hint = true,
idempotent_hint = true,
open_world_hint = true,
)
)]
pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json }): Parameters<InvokeDscResourceRequest>) -> Result<Json<InvokeDscResourceResponse>, McpError> {
pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json, what_if }): Parameters<InvokeDscResourceRequest>) -> Result<Json<InvokeDscResourceResponse>, McpError> {
let result = task::spawn_blocking(move || {
let execution_kind = if what_if.unwrap_or(false) {
if !matches!(operation, DscOperation::Set | DscOperation::Delete) {
return Err(McpError::invalid_params(t!("server.invoke_dsc_resource.whatIfNotSupported"), None));
}
ExecutionKind::WhatIf
} else {
ExecutionKind::Actual
};
let mut dsc = DscManager::new();
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new(&resource_type, None, None)).unwrap_or(None) else {
return Err(McpError::invalid_request(t!("server.invoke_dsc_resource.resourceNotFound", resource = resource_type), None));
Expand All @@ -83,7 +97,7 @@ impl McpServer {
Ok(ResourceOperationResult::GetResult(result))
},
DscOperation::Set => {
let result = match resource.set(&properties_json, false, &ExecutionKind::Actual) {
let result = match resource.set(&properties_json, false, &execution_kind) {
Ok(res) => res,
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
};
Expand All @@ -97,8 +111,10 @@ impl McpServer {
Ok(ResourceOperationResult::TestResult(result))
},
DscOperation::Delete => {
match resource.delete(&properties_json, &ExecutionKind::Actual) {
Ok(_) => Ok(ResourceOperationResult::DeleteResult { success: true }),
match resource.delete(&properties_json, &execution_kind) {
Ok(DeleteResultKind::ResourceActual) => Ok(ResourceOperationResult::DeleteResult { success: true }),
Ok(DeleteResultKind::ResourceWhatIf(delete_result)) => Ok(ResourceOperationResult::DeleteWhatIfResult(delete_result)),
Ok(DeleteResultKind::SyntheticWhatIf(test_result)) => Ok(ResourceOperationResult::TestResult(test_result)),
Err(e) => Err(McpError::internal_error(e.to_string(), None)),
}
},
Expand Down
Loading
Loading