feat(transport): add which_command for cross-platform executable resolution (#774)
* feat(transport): add which_command for cross-platform executable resolution Adds a `which_command()` helper that resolves executable paths via the `which` crate before constructing a `tokio::process::Command`. This fixes Windows failures where `.cmd` shim scripts (e.g. `npx.cmd`) are not found by `Command::new()` without a fully-qualified path. Closes #456 * refactor(transport): move which_command behind opt-in feature flag Address review feedback: the `which` dependency is now gated behind a separate `which-command` feature flag instead of being bundled into `transport-child-process`. Users on Linux/macOS who don't need cross-platform executable resolution no longer pull in the extra crate. Also fixes the doc example import path to use the re-exported `rmcp::transport::which_command`.
This commit is contained in:
parent
8e22aa2de2
commit
cabf71aa74
3 changed files with 59 additions and 0 deletions
|
|
@ -81,6 +81,9 @@ tower-service = { version = "0.3", optional = true }
|
|||
# for child process transport
|
||||
process-wrap = { version = "9.0", features = ["tokio1"], optional = true }
|
||||
|
||||
# for cross-platform executable path resolution
|
||||
which = { version = "7", optional = true }
|
||||
|
||||
# for ws transport
|
||||
# tokio-tungstenite ={ version = "0.26", optional = true }
|
||||
|
||||
|
|
@ -163,6 +166,10 @@ transport-child-process = [
|
|||
"tokio/process",
|
||||
"dep:process-wrap",
|
||||
]
|
||||
which-command = [
|
||||
"transport-child-process",
|
||||
"dep:which",
|
||||
]
|
||||
transport-streamable-http-server = [
|
||||
"transport-streamable-http-server-session",
|
||||
"server-side-http",
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ pub use worker::WorkerTransport;
|
|||
|
||||
#[cfg(feature = "transport-child-process")]
|
||||
pub mod child_process;
|
||||
#[cfg(feature = "which-command")]
|
||||
pub use child_process::which_command;
|
||||
#[cfg(feature = "transport-child-process")]
|
||||
pub use child_process::{ConfigureCommandExt, TokioChildProcess};
|
||||
|
||||
|
|
|
|||
|
|
@ -233,6 +233,56 @@ impl ConfigureCommandExt for tokio::process::Command {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve the absolute path to an executable using the system `PATH`,
|
||||
/// then return a [`tokio::process::Command`] pointing at it.
|
||||
///
|
||||
/// This is especially useful on Windows where `.cmd` / `.exe` shim scripts
|
||||
/// (e.g. `npx.cmd`) are not reliably found by [`tokio::process::Command`]
|
||||
/// without a fully-qualified path.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// use rmcp::transport::{which_command, ConfigureCommandExt};
|
||||
///
|
||||
/// # fn example() -> std::io::Result<()> {
|
||||
/// let cmd = which_command("npx")?
|
||||
/// .configure(|cmd| {
|
||||
/// cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
|
||||
/// });
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "which-command")]
|
||||
pub fn which_command(
|
||||
name: impl AsRef<std::ffi::OsStr>,
|
||||
) -> std::io::Result<tokio::process::Command> {
|
||||
let resolved = which::which(name.as_ref())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
|
||||
Ok(tokio::process::Command::new(resolved))
|
||||
}
|
||||
|
||||
#[cfg(feature = "which-command")]
|
||||
#[cfg(test)]
|
||||
mod tests_which {
|
||||
#[test]
|
||||
fn which_command_resolves_known_binary() {
|
||||
// `ls` exists on every Unix system, `cmd` on Windows
|
||||
#[cfg(unix)]
|
||||
let result = super::which_command("ls");
|
||||
#[cfg(windows)]
|
||||
let result = super::which_command("cmd");
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn which_command_fails_for_nonexistent() {
|
||||
let result = super::which_command("this_binary_definitely_does_not_exist_12345");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
Loading…
Reference in a new issue