Skip to content

Commit 955230a

Browse files
author
xychen
committed
Supports python as command
Also make shell interpreter configurable
1 parent d13ba5d commit 955230a

File tree

3 files changed

+80
-18
lines changed

3 files changed

+80
-18
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ For code completion on the `commands.yaml` file, [redhat.vscode-yaml](https://ma
6666
* `required`: Whether the argument is required (defaults to `false`).
6767
* `default`: The default value for the argument, if it is not required.
6868
* `command`: The command to run. Supports [Handlebars](https://handlebarsjs.com/guide/expressions.html) templating for arguments.
69+
* `shell`: The shell used to execute the command. Defaults to "bash" on Unix-like systems and "powershell" on Windows. Also supports "python" for using Python script in the command.
6970

7071
## Built-in tools
7172

src/manifest.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ pub struct CommandSpec {
2424
#[serde(default, skip_serializing_if = "Option::is_none")]
2525
pub args: Option<Vec<ArgumentSpec>>,
2626

27+
/// The shell used to execute the command. Defaults to "bash" on Unix-like
28+
/// systems and "powershell" on Windows. Also supports "python" for using
29+
/// Python script in the command.
30+
pub shell: Option<String>,
31+
2732
/// The command template
2833
pub command: String,
2934
}

src/manifest_executor.rs

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
io::{Read, pipe},
33
path::PathBuf,
44
process::Command,
5+
str::FromStr,
56
};
67

78
use handlebars::Handlebars;
@@ -10,23 +11,6 @@ use serde_json::Value as JsonValue;
1011

1112
use crate::manifest::CommandSpec;
1213

13-
#[cfg(unix)]
14-
fn shell(command: &str) -> Command {
15-
let mut cmd = Command::new("bash");
16-
cmd.arg("-c").arg(command.replace("\r\n", "\n"));
17-
cmd
18-
}
19-
20-
#[cfg(windows)]
21-
fn shell(command: &str) -> Command {
22-
let mut cmd = Command::new("powershell");
23-
cmd.arg("-Command").arg(format!(
24-
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\r\n{}",
25-
command.replace("\r\n", "\n").replace("\n", "\r\n")
26-
));
27-
cmd
28-
}
29-
3014
impl CommandSpec {
3115
pub fn execute(
3216
&self,
@@ -44,7 +28,13 @@ impl CommandSpec {
4428
McpError::internal_error(format!("Failed creating stdio pipes: {}", e), None)
4529
})?;
4630

47-
let mut proc = shell(&command)
31+
let shell = match self.shell {
32+
None => Shell::default(),
33+
Some(ref s) => Shell::from_str(s)?,
34+
};
35+
36+
let mut proc = shell
37+
.to_command(&command)
4838
.current_dir(cwd)
4939
.stdout(writer.try_clone().map_err(|e| {
5040
McpError::internal_error(
@@ -72,3 +62,69 @@ impl CommandSpec {
7262
Ok((command, output, exit_code))
7363
}
7464
}
65+
66+
enum Shell {
67+
Bash,
68+
PowerShell,
69+
Python,
70+
}
71+
72+
impl Default for Shell {
73+
fn default() -> Self {
74+
if cfg!(windows) {
75+
Shell::PowerShell
76+
} else {
77+
Shell::Bash
78+
}
79+
}
80+
}
81+
82+
impl FromStr for Shell {
83+
type Err = McpError;
84+
85+
fn from_str(s: &str) -> Result<Self, Self::Err> {
86+
match s.to_lowercase().as_str() {
87+
"bash" => Ok(Shell::Bash),
88+
"powershell" => Ok(Shell::PowerShell),
89+
"python" => Ok(Shell::Python),
90+
_ => Err(McpError::invalid_params(
91+
format!("Unsupported shell: {}", s),
92+
None,
93+
)),
94+
}
95+
}
96+
}
97+
98+
impl Shell {
99+
pub fn to_command(&self, command: &str) -> Command {
100+
match self {
101+
Shell::Bash => {
102+
let mut cmd = Command::new("bash");
103+
cmd.arg("-c").arg(normalize_newlines(command, false));
104+
cmd
105+
}
106+
Shell::PowerShell => {
107+
let mut cmd = Command::new("powershell");
108+
cmd.arg("-Command").arg(format!(
109+
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\r\n{}",
110+
normalize_newlines(command, true)
111+
));
112+
cmd
113+
}
114+
Shell::Python => {
115+
let mut cmd = Command::new("python");
116+
cmd.arg("-c").arg(normalize_newlines(command, false));
117+
cmd
118+
}
119+
}
120+
}
121+
}
122+
123+
fn normalize_newlines(command: &str, wants_cr_lf: bool) -> String {
124+
let command = command.replace("\r\n", "\n");
125+
if wants_cr_lf {
126+
command.replace("\n", "\r\n")
127+
} else {
128+
command
129+
}
130+
}

0 commit comments

Comments
 (0)