Skip to content

Commit 45b1007

Browse files
authored
feat: Add --what-if in MCP server tools (#1697)
1 parent 9a917a8 commit 45b1007

6 files changed

Lines changed: 347 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ changes since the last release, see the [diff on GitHub][unreleased].
7575
for the `v3.0.0.0-alpha.5` release. Leave the release links under the release section.
7676
-->
7777

78+
### Added
79+
80+
- Added what-if support to the MCP server tools. The `invoke_dsc_config` tool now accepts a
81+
`what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if`
82+
for the `set` and `delete` operations. This mirrors the `--what-if` flag on the `dsc config set`,
83+
`dsc resource set`, and `dsc resource delete` commands, enabling AI agents to preview changes
84+
before applying them. Passing `what_if` with an operation that doesn't support it returns an
85+
invalid parameters error.
86+
7887
## [v3.2.2][release-v3.2.2] - 2026-06-16
7988

8089
This section includes a summary of changes for the `3.2.2` release. For the full list of changes

docs/concepts/dsc-mcp-server.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ This enhances the overall authoring experience by providing contextual informati
2020
local DSC environment directly to AI-powered tools.
2121

2222
> [!IMPORTANT]
23-
> The DSC MCP server is focused on discovery and information retrieval. It does not
24-
> directly perform any configuration changes or resource modifications unless requested to do so.
25-
> The information it provides to AI agents can be used to generate configurations and commands
26-
> that, when executed, will impact your system. Always review and validate any generated
23+
> The DSC MCP server is primarily focused on discovery and information retrieval. It only
24+
> performs configuration changes or resource modifications when an agent explicitly invokes the
25+
> `invoke_dsc_config` or `invoke_dsc_resource` tools with the `set` or `delete` operation. Those
26+
> tools support a `what_if` option so agents can preview a change before applying it. The
27+
> information the server provides to AI agents can be used to generate configurations and
28+
> commands that, when executed, will impact your system. Always review and validate any generated
2729
> content before execution.
2830
2931
## What is Model Context Protocol (MCP)?
@@ -74,6 +76,24 @@ to help solve your specific needs:
7476

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

79+
### Previewing changes with what-if
80+
81+
Before an agent applies a configuration or resource change, it can simulate the change to show
82+
you what would happen without modifying your system. The `invoke_dsc_config` tool accepts a
83+
`what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if`
84+
for the `set` and `delete` operations. This is the same behavior as the `--what-if` flag on the
85+
`dsc config set`, `dsc resource set`, and `dsc resource delete` commands:
86+
87+
- **You ask**: "Show me what would change if I applied this configuration"
88+
- **Agent invokes**: `invoke_dsc_config` with `operation: set` and `what_if: true`
89+
- **Agent provides**: The projected before and after state for each resource, with the result
90+
metadata reporting `executionType` as `whatIf`
91+
92+
Resources that natively support what-if run their simulation directly. For resources that don't,
93+
DSC generates a synthetic what-if result from the resource's `test` operation. When `what_if` is
94+
requested with an operation that doesn't support it, such as `get`, the tool returns an error
95+
instead of silently ignoring the option.
96+
7797
> [!NOTE]
7898
> Additional MCP tools will become available in future releases to expand the capabilities
7999
> of the DSC MCP server integration. For the latest updates and feature announcements,
@@ -194,6 +214,7 @@ Example prompts that work well with DSC MCP integration:
194214
- "What DSC resources are available on this machine?"
195215
- "Show me the schema for the Microsoft.Windows/Registry resource"
196216
- "List all available DSC functions I can use in expressions"
217+
- "Preview what this configuration would change before applying it"
197218

198219
:::image type="complex" source="media/dsc-mcp-server/dsc-mcp-usage-example.png" alt-text="Screenshot showing DSC MCP usage example in VS Code":::
199220
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.

dsc/locales/en-us.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ invalidParameters = "Invalid parameters"
102102
failedConvertJson = "Failed to convert to JSON"
103103
failedSerialize = "Failed to serialize configuration"
104104
failedSetParameters = "Failed to set parameters"
105+
whatIfOnlySet = "what_if is only supported for the 'set' operation"
105106

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

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

117119
[server.list_dsc_functions]
118120
invalidNamePattern = "Invalid function name pattern '%{pattern}'"

dsc/src/server/invoke_dsc_config.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::server::mcp_server::McpServer;
55
use dsc_lib::{
66
configure::{
7-
config_doc::Configuration,
7+
config_doc::{Configuration, ExecutionKind},
88
config_result::{
99
ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult,
1010
ConfigurationTestResult,
@@ -52,14 +52,19 @@ pub struct InvokeDscConfigRequest {
5252
description = "Optional parameters to pass to the configuration as a YAML string"
5353
)]
5454
pub parameters: Option<String>,
55+
#[schemars(
56+
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."
57+
)]
58+
#[serde(default)]
59+
pub what_if: Option<bool>,
5560
}
5661

5762
#[tool_router(router = invoke_dsc_config_router, vis = "pub")]
5863
impl McpServer {
5964
#[tool(
60-
description = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters",
65+
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.",
6166
annotations(
62-
title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters",
67+
title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters and what-if support",
6368
read_only_hint = false,
6469
destructive_hint = true,
6570
idempotent_hint = true,
@@ -72,6 +77,7 @@ impl McpServer {
7277
operation,
7378
configuration,
7479
parameters,
80+
what_if,
7581
}): Parameters<InvokeDscConfigRequest>,
7682
) -> Result<Json<InvokeDscConfigResponse>, McpError> {
7783
let result = task::spawn_blocking(move || {
@@ -127,6 +133,16 @@ impl McpServer {
127133

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

136+
if what_if.unwrap_or(false) {
137+
if !matches!(operation, ConfigOperation::Set) {
138+
return Err(McpError::invalid_params(
139+
t!("server.invoke_dsc_config.whatIfOnlySet"),
140+
None,
141+
));
142+
}
143+
configurator.context.execution_type = ExecutionKind::WhatIf;
144+
}
145+
130146
let parameters_value: Option<serde_json::Value> = if let Some(params_str) = parameters {
131147
let params_json = match serde_yaml::from_str::<serde_yaml::Value>(&params_str) {
132148
Ok(yaml) => match serde_json::to_value(yaml) {

dsc/src/server/invoke_dsc_resource.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use dsc_lib::{
88
dscresources::{
99
dscresource::Invoke,
1010
invoke_result::{
11+
DeleteResult,
12+
DeleteResultKind,
1113
ExportResult,
1214
GetResult,
1315
SetResult,
@@ -39,6 +41,7 @@ pub enum ResourceOperationResult {
3941
TestResult(TestResult),
4042
ExportResult(ExportResult),
4143
DeleteResult { success: bool },
44+
DeleteWhatIfResult(DeleteResult),
4245
}
4346

4447
#[derive(Serialize, JsonSchema)]
@@ -54,22 +57,33 @@ pub struct InvokeDscResourceRequest {
5457
pub resource_type: FullyQualifiedTypeName,
5558
#[schemars(description = "The properties to pass to the DSC resource as JSON. Must match the resource JSON schema from `show_dsc_resource` tool.")]
5659
pub properties_json: String,
60+
#[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.")]
61+
#[serde(default)]
62+
pub what_if: Option<bool>,
5763
}
5864

5965
#[tool_router(router = invoke_dsc_resource_router, vis = "pub")]
6066
impl McpServer {
6167
#[tool(
62-
description = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format",
68+
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.",
6369
annotations(
64-
title = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format",
70+
title = "Invoke a DSC resource operation (Get, Set, Test, Export, Delete) with specified properties in JSON format and what-if support",
6571
read_only_hint = false,
6672
destructive_hint = true,
6773
idempotent_hint = true,
6874
open_world_hint = true,
6975
)
7076
)]
71-
pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json }): Parameters<InvokeDscResourceRequest>) -> Result<Json<InvokeDscResourceResponse>, McpError> {
77+
pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json, what_if }): Parameters<InvokeDscResourceRequest>) -> Result<Json<InvokeDscResourceResponse>, McpError> {
7278
let result = task::spawn_blocking(move || {
79+
let execution_kind = if what_if.unwrap_or(false) {
80+
if !matches!(operation, DscOperation::Set | DscOperation::Delete) {
81+
return Err(McpError::invalid_params(t!("server.invoke_dsc_resource.whatIfNotSupported"), None));
82+
}
83+
ExecutionKind::WhatIf
84+
} else {
85+
ExecutionKind::Actual
86+
};
7387
let mut dsc = DscManager::new();
7488
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new(&resource_type, None, None)).unwrap_or(None) else {
7589
return Err(McpError::invalid_request(t!("server.invoke_dsc_resource.resourceNotFound", resource = resource_type), None));
@@ -83,7 +97,7 @@ impl McpServer {
8397
Ok(ResourceOperationResult::GetResult(result))
8498
},
8599
DscOperation::Set => {
86-
let result = match resource.set(&properties_json, false, &ExecutionKind::Actual) {
100+
let result = match resource.set(&properties_json, false, &execution_kind) {
87101
Ok(res) => res,
88102
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
89103
};
@@ -97,8 +111,10 @@ impl McpServer {
97111
Ok(ResourceOperationResult::TestResult(result))
98112
},
99113
DscOperation::Delete => {
100-
match resource.delete(&properties_json, &ExecutionKind::Actual) {
101-
Ok(_) => Ok(ResourceOperationResult::DeleteResult { success: true }),
114+
match resource.delete(&properties_json, &execution_kind) {
115+
Ok(DeleteResultKind::ResourceActual) => Ok(ResourceOperationResult::DeleteResult { success: true }),
116+
Ok(DeleteResultKind::ResourceWhatIf(delete_result)) => Ok(ResourceOperationResult::DeleteWhatIfResult(delete_result)),
117+
Ok(DeleteResultKind::SyntheticWhatIf(test_result)) => Ok(ResourceOperationResult::TestResult(test_result)),
102118
Err(e) => Err(McpError::internal_error(e.to_string(), None)),
103119
}
104120
},

0 commit comments

Comments
 (0)