sac-cli 0.1.0

Terminal-based AI coding agent — fork of NAC with extended backend support and context management
Documentation
use super::*;

pub(super) async fn connect_server(
    name: &str,
    config: &McpServerConfig,
    handler: &NacMcpClientHandler,
    sandbox: Option<&SandboxSession>,
) -> Result<McpService> {
    match config.transport.clone() {
        McpTransportConfig::Stdio { command, args, env } => {
            let command = expand_env(&command)?;
            let args = expand_strings(&args)?;
            let env = expand_map(&env)?;
            let transport =
                TokioChildProcess::new(build_stdio_command(&command, &args, &env, sandbox)?)?;
            handler
                .clone()
                .serve(transport)
                .await
                .with_context(|| format!("failed to connect stdio MCP server '{}'", name))
        }
        McpTransportConfig::StreamableHttp { url, headers } => {
            let url = expand_env(&url)?;
            let transport = StreamableHttpClientTransport::from_config(
                build_http_transport_config(&url, &headers)?,
            );
            handler
                .clone()
                .serve(transport)
                .await
                .with_context(|| format!("failed to connect HTTP MCP server '{}'", name))
        }
    }
}

fn build_stdio_command(
    program: &str,
    args: &[String],
    envs: &BTreeMap<String, String>,
    sandbox: Option<&SandboxSession>,
) -> Result<Command> {
    let env_pairs: Vec<(String, String)> = envs
        .iter()
        .map(|(key, value)| (key.clone(), value.clone()))
        .collect();

    if let Some(sandbox) = sandbox {
        return Ok(sandbox.child_process_command(program, args, &env_pairs));
    }

    let mut command = Command::new(program);
    command.args(args);
    command.envs(envs);
    command.stdin(std::process::Stdio::piped());
    command.stdout(std::process::Stdio::piped());
    command.stderr(std::process::Stdio::inherit());
    Ok(command)
}

fn build_http_transport_config(
    url: &str,
    headers: &BTreeMap<String, String>,
) -> Result<StreamableHttpClientTransportConfig> {
    let mut custom_headers = HashMap::new();
    for (name, value) in headers {
        let name = HeaderName::from_bytes(name.as_bytes())
            .with_context(|| format!("invalid HTTP header name '{}'", name))?;
        let value = HeaderValue::from_str(&expand_env(value)?)
            .with_context(|| format!("invalid HTTP header value for '{}'", name))?;
        custom_headers.insert(name, value);
    }
    Ok(StreamableHttpClientTransportConfig::with_uri(url).custom_headers(custom_headers))
}