Skip to content

Commit 025122a

Browse files
authored
Merge branch 'PowerShell:main' into gh-57/main/add-objectkey-function
2 parents 7edd993 + de79d34 commit 025122a

File tree

21 files changed

+1506
-169
lines changed

21 files changed

+1506
-169
lines changed

.github/instructions/instructions.md

Lines changed: 459 additions & 0 deletions
Large diffs are not rendered by default.

dsc/locales/en-us.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ failedReadingParametersFile = "Failed to read parameters file"
4444
readingParametersFromStdin = "Reading parameters from STDIN"
4545
generatingCompleter = "Generating completion script for"
4646
readingParametersFile = "Reading parameters from file"
47+
mergingParameters = "Merging inline parameters with parameters file (inline takes precedence)"
48+
failedMergingParameters = "Failed to merge parameters"
4749
usingDscVersion = "Running DSC version"
4850
foundProcesses = "Found processes"
4951
failedToGetPid = "Could not get current process id"
@@ -66,6 +68,13 @@ serverStopped = "MCP server stopped"
6668
failedToCreateRuntime = "Failed to create async runtime: %{error}"
6769
serverWaitFailed = "Failed to wait for MCP server: %{error}"
6870

71+
[mcp.invoke_dsc_config]
72+
invalidConfiguration = "Invalid configuration document"
73+
invalidParameters = "Invalid parameters"
74+
failedConvertJson = "Failed to convert to JSON"
75+
failedSerialize = "Failed to serialize configuration"
76+
failedSetParameters = "Failed to set parameters"
77+
6978
[mcp.invoke_dsc_resource]
7079
resourceNotFound = "Resource type '%{resource}' does not exist"
7180

@@ -158,5 +167,5 @@ failedToAbsolutizePath = "Error making config path absolute"
158167
failedToGetParentPath = "Error reading config path parent"
159168
dscConfigRootAlreadySet = "The current value of DSC_CONFIG_ROOT env var will be overridden"
160169
settingDscConfigRoot = "Setting DSC_CONFIG_ROOT env var as"
161-
stdinNotAllowedForBothParametersAndInput = "Cannot read from STDIN for both parameters and input."
162170
removingUtf8Bom = "Removing UTF-8 BOM from input"
171+
parametersNotObject = "Parameters must be an object"

dsc/src/args.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ pub enum SubCommand {
6666
Config {
6767
#[clap(subcommand)]
6868
subcommand: ConfigSubCommand,
69-
#[clap(short, long, help = t!("args.parameters").to_string(), conflicts_with = "parameters_file")]
69+
#[clap(short, long, help = t!("args.parameters").to_string())]
7070
parameters: Option<String>,
71-
#[clap(short = 'f', long, help = t!("args.parametersFile").to_string(), conflicts_with = "parameters")]
71+
#[clap(short = 'f', long, help = t!("args.parametersFile").to_string())]
7272
parameters_file: Option<String>,
7373
#[clap(short = 'r', long, help = t!("args.systemRoot").to_string())]
7474
system_root: Option<String>,

dsc/src/main.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,15 @@ fn main() {
5454
generate(shell, &mut cmd, "dsc", &mut io::stdout());
5555
},
5656
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
57-
if let Some(file_name) = parameters_file {
57+
// Read parameters from file if provided
58+
let file_params = if let Some(file_name) = &parameters_file {
5859
if file_name == "-" {
5960
info!("{}", t!("main.readingParametersFromStdin"));
6061
let mut stdin = Vec::<u8>::new();
61-
let parameters = match io::stdin().read_to_end(&mut stdin) {
62+
match io::stdin().read_to_end(&mut stdin) {
6263
Ok(_) => {
6364
match String::from_utf8(stdin) {
64-
Ok(input) => {
65-
input
66-
},
65+
Ok(input) => Some(input),
6766
Err(err) => {
6867
error!("{}: {err}", t!("util.invalidUtf8"));
6968
exit(EXIT_INVALID_INPUT);
@@ -74,22 +73,38 @@ fn main() {
7473
error!("{}: {err}", t!("util.failedToReadStdin"));
7574
exit(EXIT_INVALID_INPUT);
7675
}
77-
};
78-
subcommand::config(&subcommand, &Some(parameters), true, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
79-
return;
80-
}
81-
info!("{}: {file_name}", t!("main.readingParametersFile"));
82-
match std::fs::read_to_string(&file_name) {
83-
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
84-
Err(err) => {
85-
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
86-
exit(util::EXIT_INVALID_INPUT);
76+
}
77+
} else {
78+
info!("{}: {file_name}", t!("main.readingParametersFile"));
79+
match std::fs::read_to_string(file_name) {
80+
Ok(content) => Some(content),
81+
Err(err) => {
82+
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
83+
exit(util::EXIT_INVALID_INPUT);
84+
}
8785
}
8886
}
89-
}
90-
else {
91-
subcommand::config(&subcommand, &parameters, false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
92-
}
87+
} else {
88+
None
89+
};
90+
91+
let merged_parameters = match (file_params, parameters) {
92+
(Some(file_content), Some(inline_content)) => {
93+
info!("{}", t!("main.mergingParameters"));
94+
match util::merge_parameters(&file_content, &inline_content) {
95+
Ok(merged) => Some(merged),
96+
Err(err) => {
97+
error!("{}: {err}", t!("main.failedMergingParameters"));
98+
exit(EXIT_INVALID_INPUT);
99+
}
100+
}
101+
},
102+
(Some(file_content), None) => Some(file_content),
103+
(None, Some(inline_content)) => Some(inline_content),
104+
(None, None) => None,
105+
};
106+
107+
subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
93108
},
94109
SubCommand::Extension { subcommand } => {
95110
subcommand::extension(&subcommand, progress_format);

dsc/src/mcp/invoke_dsc_config.rs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::mcp::mcp_server::McpServer;
5+
use dsc_lib::{
6+
configure::{
7+
config_doc::Configuration,
8+
config_result::{
9+
ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult,
10+
ConfigurationTestResult,
11+
},
12+
Configurator,
13+
},
14+
progress::ProgressFormat,
15+
};
16+
use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json};
17+
use rust_i18n::t;
18+
use schemars::JsonSchema;
19+
use serde::{Deserialize, Serialize};
20+
use tokio::task;
21+
22+
#[derive(Deserialize, JsonSchema)]
23+
#[serde(rename_all = "lowercase")]
24+
pub enum ConfigOperation {
25+
Get,
26+
Set,
27+
Test,
28+
Export,
29+
}
30+
31+
#[derive(Serialize, JsonSchema)]
32+
#[serde(untagged)]
33+
pub enum ConfigOperationResult {
34+
GetResult(Box<ConfigurationGetResult>),
35+
SetResult(Box<ConfigurationSetResult>),
36+
TestResult(Box<ConfigurationTestResult>),
37+
ExportResult(Box<ConfigurationExportResult>),
38+
}
39+
40+
#[derive(Serialize, JsonSchema)]
41+
pub struct InvokeDscConfigResponse {
42+
pub result: ConfigOperationResult,
43+
}
44+
45+
#[derive(Deserialize, JsonSchema)]
46+
pub struct InvokeDscConfigRequest {
47+
#[schemars(description = "The operation to perform on the DSC configuration")]
48+
pub operation: ConfigOperation,
49+
#[schemars(description = "The DSC configuration document as a YAML string")]
50+
pub configuration: String,
51+
#[schemars(
52+
description = "Optional parameters to pass to the configuration as a YAML string"
53+
)]
54+
pub parameters: Option<String>,
55+
}
56+
57+
#[tool_router(router = invoke_dsc_config_router, vis = "pub")]
58+
impl McpServer {
59+
#[tool(
60+
description = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters",
61+
annotations(
62+
title = "Invoke a DSC configuration operation (Get, Set, Test, Export) with optional parameters",
63+
read_only_hint = false,
64+
destructive_hint = true,
65+
idempotent_hint = true,
66+
open_world_hint = true,
67+
)
68+
)]
69+
pub async fn invoke_dsc_config(
70+
&self,
71+
Parameters(InvokeDscConfigRequest {
72+
operation,
73+
configuration,
74+
parameters,
75+
}): Parameters<InvokeDscConfigRequest>,
76+
) -> Result<Json<InvokeDscConfigResponse>, McpError> {
77+
let result = task::spawn_blocking(move || {
78+
let config: Configuration = match serde_yaml::from_str::<serde_yaml::Value>(&configuration) {
79+
Ok(yaml_value) => match serde_json::to_value(yaml_value) {
80+
Ok(json_value) => match serde_json::from_value(json_value) {
81+
Ok(config) => config,
82+
Err(e) => {
83+
return Err(McpError::invalid_request(
84+
format!(
85+
"{}: {e}",
86+
t!("mcp.invoke_dsc_config.invalidConfiguration")
87+
),
88+
None,
89+
))
90+
}
91+
},
92+
Err(e) => {
93+
return Err(McpError::invalid_request(
94+
format!(
95+
"{}: {e}",
96+
t!("mcp.invoke_dsc_config.failedConvertJson")
97+
),
98+
None,
99+
))
100+
}
101+
},
102+
Err(e) => {
103+
return Err(McpError::invalid_request(
104+
format!(
105+
"{}: {e}",
106+
t!("mcp.invoke_dsc_config.invalidConfiguration")
107+
),
108+
None,
109+
))
110+
}
111+
};
112+
113+
let config_json = match serde_json::to_string(&config) {
114+
Ok(json) => json,
115+
Err(e) => {
116+
return Err(McpError::internal_error(
117+
format!("{}: {e}", t!("mcp.invoke_dsc_config.failedSerialize")),
118+
None,
119+
))
120+
}
121+
};
122+
123+
let mut configurator = match Configurator::new(&config_json, ProgressFormat::None) {
124+
Ok(configurator) => configurator,
125+
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
126+
};
127+
128+
configurator.context.dsc_version = Some(env!("CARGO_PKG_VERSION").to_string());
129+
130+
let parameters_value: Option<serde_json::Value> = if let Some(params_str) = parameters {
131+
let params_json = match serde_yaml::from_str::<serde_yaml::Value>(&params_str) {
132+
Ok(yaml) => match serde_json::to_value(yaml) {
133+
Ok(json) => json,
134+
Err(e) => {
135+
return Err(McpError::invalid_request(
136+
format!(
137+
"{}: {e}",
138+
t!("mcp.invoke_dsc_config.failedConvertJson")
139+
),
140+
None,
141+
))
142+
}
143+
},
144+
Err(e) => {
145+
return Err(McpError::invalid_request(
146+
format!(
147+
"{}: {e}",
148+
t!("mcp.invoke_dsc_config.invalidParameters")
149+
),
150+
None,
151+
))
152+
}
153+
};
154+
155+
// Wrap parameters in a "parameters" field for configurator.set_context()
156+
Some(serde_json::json!({
157+
"parameters": params_json
158+
}))
159+
} else {
160+
None
161+
};
162+
163+
if let Err(e) = configurator.set_context(parameters_value.as_ref()) {
164+
return Err(McpError::invalid_request(
165+
format!("{}: {e}", t!("mcp.invoke_dsc_config.failedSetParameters")),
166+
None,
167+
));
168+
}
169+
170+
match operation {
171+
ConfigOperation::Get => {
172+
let result = match configurator.invoke_get() {
173+
Ok(res) => res,
174+
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
175+
};
176+
Ok(ConfigOperationResult::GetResult(Box::new(result)))
177+
}
178+
ConfigOperation::Set => {
179+
let result = match configurator.invoke_set(false) {
180+
Ok(res) => res,
181+
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
182+
};
183+
Ok(ConfigOperationResult::SetResult(Box::new(result)))
184+
}
185+
ConfigOperation::Test => {
186+
let result = match configurator.invoke_test() {
187+
Ok(res) => res,
188+
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
189+
};
190+
Ok(ConfigOperationResult::TestResult(Box::new(result)))
191+
}
192+
ConfigOperation::Export => {
193+
let result = match configurator.invoke_export() {
194+
Ok(res) => res,
195+
Err(e) => return Err(McpError::internal_error(e.to_string(), None)),
196+
};
197+
Ok(ConfigOperationResult::ExportResult(Box::new(result)))
198+
}
199+
}
200+
})
201+
.await
202+
.map_err(|e| McpError::internal_error(e.to_string(), None))??;
203+
204+
Ok(Json(InvokeDscConfigResponse { result }))
205+
}
206+
}

dsc/src/mcp/mcp_server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ impl McpServer {
2121
pub fn new() -> Self {
2222
Self {
2323
tool_router:
24-
Self::invoke_dsc_resource_router()
24+
Self::invoke_dsc_config_router()
25+
+ Self::invoke_dsc_resource_router()
2526
+ Self::list_dsc_functions_router()
2627
+ Self::list_dsc_resources_router()
2728
+ Self::show_dsc_resource_router()

dsc/src/mcp/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rmcp::{
99
};
1010
use rust_i18n::t;
1111

12+
pub mod invoke_dsc_config;
1213
pub mod invoke_dsc_resource;
1314
pub mod list_dsc_functions;
1415
pub mod list_dsc_resources;

0 commit comments

Comments
 (0)