@ondeinference / ed / commits / db0039c

Skip the stdio child-process transport on watchOS

watchOS hides execv the same way tvOS does, so nix under process-wrap doesn't compile there. Gate the rmcp child-process feature, the TokioChildProcess import and the Transport::Stdio arm on any(tvos, watchos). Http stays available on both. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Seto Elkahfi committed Oct 16, 2026 at 09:17 UTC db0039c6dd97a5c4332cbd32d5652c8e9479a50f
2 files changed +14 -14
crates/ed-mcp/Cargo.toml
+4 -4
@@ -24,10 +24,10 @@ serde_json.workspace = true
24 anyhow.workspace = true
25 tracing.workspace = true
26
27 -# Spawning an MCP server as a child process needs `exec`, which Apple hides on tvOS (`libc`
28 -# has no `execv` there, so `nix` under `process-wrap` doesn't compile). The stdio transport
29 -# is everywhere else.
30 -[target.'cfg(not(target_os = "tvos"))'.dependencies]
27 +# Spawning an MCP server as a child process needs `exec`, which Apple hides on tvOS and
28 +# watchOS (`libc` has no `execv` there, so `nix` under `process-wrap` doesn't compile). The
29 +# stdio transport is everywhere else.
30 +[target.'cfg(not(any(target_os = "tvos", target_os = "watchos")))'.dependencies]
31 rmcp = { workspace = true, features = ["transport-child-process"] }
32
33 [features]
crates/ed-mcp/src/lib.rs
+10 -10
@@ -35,7 +35,7 @@ use rmcp::model::{
35 };
36 use rmcp::service::{NotificationContext, PeerRequestOptions, RequestContext, RunningService};
37 use rmcp::transport::StreamableHttpClientTransport;
38 -#[cfg(not(target_os = "tvos"))]
38 +#[cfg(not(any(target_os = "tvos", target_os = "watchos")))]
39 use rmcp::transport::TokioChildProcess;
40 use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
41 use rmcp::{ClientHandler, ErrorData as McpError, RoleClient, ServiceExt};
@@ -144,12 +144,12 @@ pub enum Transport {
144 /// Start `command args…` with extra environment variables and talk over its stdio.
145 ///
146 /// Stdio is only available on platforms that can spawn a child process.
147 - /// On `target_os = "tvos"` the OS does not allow apps to create other
148 - /// processes, so this variant is **not** supported on tvOS builds: the
149 - /// matching arm in [`Connection::connect_with`] returns an error at
150 - /// runtime instead of compiling out the variant. Use [`Transport::Http`]
151 - /// for tvOS; if you only know the transport at runtime, branch on the
152 - /// current `target_os` before constructing a `Stdio` spec.
147 + /// On `target_os = "tvos"` and `target_os = "watchos"` the OS does not allow
148 + /// apps to create other processes, so this variant is **not** supported on
149 + /// tvOS or watchOS builds: the matching arm in [`Connection::connect_with`]
150 + /// returns an error at runtime instead of compiling out the variant. Use
151 + /// [`Transport::Http`] there; if you only know the transport at runtime,
152 + /// branch on the current `target_os` before constructing a `Stdio` spec.
153 Stdio {
154 command: PathBuf,
155 args: Vec<String>,
@@ -360,7 +360,7 @@ impl Connection {
360 elicitation,
361 };
362 let handshake = match &spec.transport {
363 - #[cfg(not(target_os = "tvos"))]
363 + #[cfg(not(any(target_os = "tvos", target_os = "watchos")))]
364 Transport::Stdio { command, args, env } => {
365 let mut cmd = tokio::process::Command::new(command);
366 cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
@@ -368,8 +368,8 @@ impl Connection {
368 .with_context(|| format!("starting {}", command.display()))?;
369 tokio::time::timeout(timeout, handler.serve(transport)).await
370 }
371 - // tvOS can't start a process, so there is no stdio transport to offer.
372 - #[cfg(target_os = "tvos")]
371 + // tvOS and watchOS can't start a process, so there is no stdio transport to offer.
372 + #[cfg(any(target_os = "tvos", target_os = "watchos"))]
373 Transport::Stdio { command, .. } => {
374 bail!(
375 "can't start {}: this platform can't run MCP servers as processes",