use anyhow::Result;
use std::collections::HashMap;
use std::process::Stdio;
use crate::proxy::{StreamProxyHandler, ToolFilter};
use mcp_streamable_proxy::{
BackendNotificationBridge, ServiceExt, TokioChildProcess, UpstreamPeerRegistry, stdio,
};
use crate::client::support::utils::truncate_str;
use process_wrap::tokio::{CommandWrap, KillOnDrop};
#[cfg(unix)]
use process_wrap::tokio::ProcessGroup;
#[cfg(windows)]
use process_wrap::tokio::JobObject;
pub async fn run_command_mode(
name: &str,
command: &str,
cmd_args: Vec<String>,
env: HashMap<String, String>,
tool_filter: ToolFilter,
quiet: bool,
) -> Result<()> {
tracing::info!("Running in local command mode");
tracing::info!("Command: {} {:?}", command, cmd_args);
if !env.is_empty() {
tracing::debug!("Env var count: {}", env.len());
}
if !quiet {
eprintln!("🚀 MCP-Stdio-Proxy: {} (command) → stdio", name);
eprintln!(" Command: {} {:?}", command, cmd_args);
if !env.is_empty() {
eprintln!(" Env vars: {:?}", env);
}
}
if !quiet && tool_filter.is_enabled() {
eprintln!("🔧 Tool filtering enabled");
}
tracing::debug!("[child-process] {} {:?}", command, cmd_args);
let mut wrapped_cmd = CommandWrap::with_new(command, |cmd| {
cmd.args(&cmd_args);
for (k, v) in &env {
cmd.env(k, v);
}
});
#[cfg(unix)]
wrapped_cmd.wrap(ProcessGroup::leader());
#[cfg(windows)]
{
use process_wrap::tokio::CreationFlags;
use windows::Win32::System::Threading::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};
wrapped_cmd.wrap(CreationFlags(CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP));
wrapped_cmd.wrap(JobObject);
}
wrapped_cmd.wrap(KillOnDrop);
tracing::debug!("Starting child process...");
let (tokio_process, child_stderr) = TokioChildProcess::builder(wrapped_cmd)
.stderr(Stdio::piped())
.spawn()?;
if let Some(stderr_pipe) = child_stderr {
mcp_common::spawn_stderr_reader(stderr_pipe, name.to_string());
}
if !quiet {
eprintln!("🔗 Starting child process...");
}
let bridge = BackendNotificationBridge::with_default_info(std::sync::Arc::new(
UpstreamPeerRegistry::new(),
));
let running = bridge.serve(tokio_process).await?;
if !quiet {
eprintln!("✅ Child process started, proxying to stdio...");
match running.list_tools(None).await {
Ok(tools_result) => {
let tools = &tools_result.tools;
if tools.is_empty() {
eprintln!("⚠️ Tool list is empty (tools/list returned 0 tools)");
} else {
eprintln!("🔧 Available tools ({}):", tools.len());
for tool in tools {
let desc = tool.description.as_deref().unwrap_or("no description");
let desc_short = truncate_str(desc, 50);
eprintln!(" - {} : {}", tool.name, desc_short);
}
}
}
Err(e) => {
eprintln!("⚠️ Failed to list tools: {}", e);
}
}
eprintln!("💡 You can now send JSON-RPC requests through stdin");
}
let proxy_handler =
StreamProxyHandler::with_tool_filter(running, name.to_string(), tool_filter);
let server = proxy_handler.serve(stdio()).await?;
tokio::select! {
result = server.waiting() => {
result?;
}
_ = tokio::signal::ctrl_c() => {
tracing::info!("Ctrl+C received, shutting down");
}
}
Ok(())
}