Skip to content

Commit 5aea911

Browse files
committed
feat: support windows processes in mcp_client
1 parent 06b1bfb commit 5aea911

File tree

9 files changed

+61
-17
lines changed

9 files changed

+61
-17
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/chat-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
6565
dirs = "5.0.0"
6666
eyre = "0.6.8"
6767
fd-lock = "4.0.4"
68+
fig_util.workspace = true
6869
futures = "0.3.26"
6970
glob = "0.3.2"
7071
globset = "0.4.16"

crates/chat-cli/src/mcp_client/client.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ use std::sync::{
1111
};
1212
use std::time::Duration;
1313

14-
#[cfg(unix)]
15-
use nix::sys::signal::Signal;
16-
#[cfg(unix)]
17-
use nix::unistd::Pid;
14+
use fig_util::process_info::{
15+
Pid,
16+
terminate_process,
17+
};
1818
use serde::{
1919
Deserialize,
2020
Serialize,
@@ -114,7 +114,6 @@ pub struct Client<T: Transport> {
114114
server_name: String,
115115
transport: Arc<T>,
116116
timeout: u64,
117-
#[cfg(unix)]
118117
server_process_id: Option<Pid>,
119118
client_info: serde_json::Value,
120119
current_id: Arc<AtomicU64>,
@@ -130,7 +129,6 @@ impl<T: Transport> Clone for Client<T> {
130129
timeout: self.timeout,
131130
// Note that we cannot have an id for the clone because we would kill the original
132131
// process when we drop the clone
133-
#[cfg(unix)]
134132
server_process_id: None,
135133
client_info: self.client_info.clone(),
136134
current_id: self.current_id.clone(),
@@ -158,7 +156,7 @@ impl Client<StdioTransport> {
158156
.stderr(Stdio::piped())
159157
.envs(std::env::vars());
160158

161-
#[cfg(unix)]
159+
#[cfg(not(windows))]
162160
command.process_group(0);
163161

164162
if let Some(env) = env {
@@ -169,21 +167,22 @@ impl Client<StdioTransport> {
169167
command.args(args).spawn()?
170168
};
171169

172-
#[cfg(unix)]
170+
let server_process_id = child.id().ok_or(ClientError::MissingProcessId)?;
171+
172+
#[cfg(windows)]
173+
let server_process_id = Some(Pid::from_raw(server_process_id)); // On Windows, child.id() returns u32 which matches Pid::from_raw
174+
#[cfg(not(windows))]
173175
let server_process_id = Some(Pid::from_raw(
174-
child
175-
.id()
176-
.ok_or(ClientError::MissingProcessId)?
176+
server_process_id
177177
.try_into()
178178
.map_err(|_err| ClientError::MissingProcessId)?,
179-
));
179+
)); // On Unix, safely convert u32 to i32
180180

181181
let transport = Arc::new(transport::stdio::JsonRpcStdioTransport::client(child)?);
182182
Ok(Self {
183183
server_name,
184184
transport,
185185
timeout,
186-
#[cfg(unix)]
187186
server_process_id,
188187
client_info,
189188
current_id: Arc::new(AtomicU64::new(0)),
@@ -193,7 +192,6 @@ impl Client<StdioTransport> {
193192
}
194193
}
195194

196-
#[cfg(unix)]
197195
impl<T> Drop for Client<T>
198196
where
199197
T: Transport,
@@ -202,7 +200,7 @@ where
202200
// This drop trait is here as a fail safe to ensure we don't leave behind any orphans.
203201
fn drop(&mut self) {
204202
if let Some(process_id) = self.server_process_id {
205-
let _ = nix::sys::signal::kill(process_id, Signal::SIGTERM);
203+
let _ = terminate_process(process_id);
206204
}
207205
}
208206
}

crates/fig_integrations/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub enum Error {
4848
PList(#[from] plist::Error),
4949
#[error("Permission denied: {}", .path.display())]
5050
PermissionDenied { path: PathBuf, inner: io::Error },
51+
#[cfg(not(windows))]
5152
#[error("nix: {}", .0)]
5253
Nix(#[from] nix::Error),
5354
#[cfg(target_os = "linux")]

crates/fig_integrations/src/ssh/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fs::{
33
File,
44
};
55
use std::io::Write;
6+
#[cfg(not(windows))]
67
use std::os::unix::fs::DirBuilderExt;
78
use std::path::PathBuf;
89

crates/fig_util/src/process_info/linux.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::path::PathBuf;
22
use std::str::FromStr;
33

4+
use nix::sys::signal::Signal;
5+
46
pub trait LinuxExt {
57
fn cmdline(&self) -> Option<String>;
68
}
@@ -10,6 +12,11 @@ use super::{
1012
PidExt,
1113
};
1214

15+
pub fn terminate_process(pid: Pid) -> Result<(), String> {
16+
let nix_pid = nix::unistd::Pid::from_raw(pid.0);
17+
nix::sys::signal::kill(nix_pid, Signal::SIGTERM).map_err(|e| format!("Failed to terminate process: {}", e))
18+
}
19+
1320
impl PidExt for Pid {
1421
fn current() -> Self {
1522
nix::unistd::getpid().into()

crates/fig_util/src/process_info/macos.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ use std::mem::MaybeUninit;
33
use std::os::unix::prelude::OsStrExt;
44
use std::path::PathBuf;
55

6+
use nix::sys::signal::Signal;
7+
68
use super::{
79
Pid,
810
PidExt,
911
};
1012

13+
/// Terminate a process on macOS
14+
pub fn terminate_process(pid: Pid) -> Result<(), String> {
15+
let nix_pid = nix::unistd::Pid::from_raw(pid.0);
16+
nix::sys::signal::kill(nix_pid, Signal::SIGTERM).map_err(|e| format!("Failed to terminate process: {}", e))
17+
}
18+
1119
impl PidExt for Pid {
1220
fn current() -> Self {
1321
nix::unistd::getpid().into()

crates/fig_util/src/process_info/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ pub use linux::*;
1313

1414
#[cfg(target_os = "macos")]
1515
mod macos;
16-
// #[cfg(target_os = "macos")]
17-
// pub use macos::*;
16+
#[cfg(target_os = "macos")]
17+
pub use macos::*;
1818

1919
#[cfg(target_os = "windows")]
2020
mod windows;
21+
#[cfg(target_os = "windows")]
22+
pub use windows::*;
2123

2224
#[cfg(target_os = "freebsd")]
2325
mod freebsd;
@@ -52,6 +54,12 @@ macro_rules! pid_decl {
5254
write!(f, "{}", self.0)
5355
}
5456
}
57+
58+
impl Pid {
59+
pub fn from_raw(raw: $typ) -> Self {
60+
Self(raw)
61+
}
62+
}
5563
};
5664
}
5765

crates/fig_util/src/process_info/windows.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ use windows::Win32::System::Threading::{
2222
PROCESS_NAME_FORMAT,
2323
PROCESS_QUERY_INFORMATION,
2424
PROCESS_QUERY_LIMITED_INFORMATION,
25+
PROCESS_TERMINATE,
2526
PROCESS_VM_READ,
2627
QueryFullProcessImageNameA,
28+
TerminateProcess,
2729
};
2830
use windows::core::PSTR;
2931

@@ -32,6 +34,23 @@ use super::{
3234
PidExt,
3335
};
3436

37+
/// Terminate a process on Windows using the Windows API
38+
pub fn terminate_process(pid: Pid) -> Result<(), String> {
39+
unsafe {
40+
// Open the process with termination rights
41+
let handle =
42+
OpenProcess(PROCESS_TERMINATE, false, pid.0).map_err(|e| format!("Failed to open process: {}", e))?;
43+
44+
// Create a safe handle that will be closed automatically when dropped
45+
let safe_handle = SafeHandle::new(handle).ok_or_else(|| "Invalid process handle".to_string())?;
46+
47+
// Terminate the process with exit code 1
48+
TerminateProcess(*safe_handle, 1).map_err(|e| format!("Failed to terminate process: {}", e))?;
49+
50+
Ok(())
51+
}
52+
}
53+
3554
struct SafeHandle(HANDLE);
3655

3756
impl SafeHandle {

0 commit comments

Comments
 (0)