magi-code 0.96.2

Repository-aware CLI coding agent for terminal work
Documentation
use crate::output::sanitize_display_text;
use crate::{
    config::{
        McPaths, McpServerConfig, McpServersSettings, read_settings, validate_mcp_server_name,
    },
    mcp::{
        McpClient,
        manager::{McpManager, McpServerStatus},
        oauth,
    },
};
use anyhow::Result;
#[cfg(not(windows))]
use std::process::Command;
use std::{
    io::Write,
    sync::{Arc, atomic::AtomicBool},
    thread,
    time::Duration,
};

pub(crate) fn run_list(stdout: &mut impl Write, stderr: &mut impl Write) -> Result<()> {
    run_list_with_paths(&McPaths::resolve()?, stdout, stderr)
}

fn run_list_with_paths(
    paths: &McPaths,
    stdout: &mut impl Write,
    stderr: &mut impl Write,
) -> Result<()> {
    let settings = read_settings(paths)?;
    writeln!(stdout, "MCP servers")?;
    writeln!(
        stdout,
        "  Definitions: CONFIG_DIR/.mcp.json or cwd/.mcp.json"
    )?;
    writeln!(
        stdout,
        "  Disabled by default; enable with /mcp in Mission Control, then restart."
    )?;
    if settings.mcp_servers.is_empty() {
        writeln!(stdout, "  none configured")?;
        return Ok(());
    }

    let manager = McpManager::from_settings_with_paths(&settings.mcp_servers, Some(&paths.root));
    for (name, config) in &settings.mcp_servers {
        let server_type = match config {
            McpServerConfig::Stdio(_) => "stdio".to_string(),
            McpServerConfig::Http(http) => format!("http {}", sanitized_url(&http.url)),
        };
        let auth = oauth_status_label(paths, name, config, &mut *stderr);
        if !config.enabled() {
            writeln!(stdout, "  {name} ({server_type}{auth}): disabled")?;
            continue;
        }
        match manager.statuses().get(name) {
            Some(McpServerStatus::Connected { tool_count, .. }) => {
                writeln!(
                    stdout,
                    "  {name} ({server_type}{auth}): connected ({tool_count} tools)"
                )?;
            }
            Some(McpServerStatus::Failed { phase, error }) => {
                writeln!(
                    stdout,
                    "  {name} ({server_type}{auth}): failed during {phase}"
                )?;
                writeln!(stderr, "mcp {name}: {phase} failed: {error}")?;
            }
            None => {
                writeln!(
                    stdout,
                    "  {name} ({server_type}{auth}): failed during startup"
                )?;
                writeln!(stderr, "mcp {name}: startup failed without status")?;
            }
        }
    }
    Ok(())
}

pub(crate) fn run_login(
    server: &str,
    stdout: &mut impl Write,
    stderr: &mut impl Write,
) -> Result<()> {
    validate_mcp_server_name(server)?;
    let paths = McPaths::resolve()?;
    let settings = read_settings(&paths)?;
    let config = settings
        .mcp_servers
        .get(server)
        .ok_or_else(|| anyhow::anyhow!("MCP server '{server}' is not configured"))?;
    let McpServerConfig::Http(http) = config else {
        anyhow::bail!("MCP server '{server}' is not an HTTP server")
    };
    let oauth_config = http
        .oauth
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("server does not have OAuth configured"))?;
    let client = reqwest::blocking::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .timeout(Duration::from_secs(
            http.timeout
                .unwrap_or(crate::config::DEFAULT_MCP_TIMEOUT_SECONDS),
        ))
        .build()?;
    let (listener, redirect_uri) =
        oauth::bind_callback_listener().map_err(|e| anyhow::anyhow!(e))?;
    let login = oauth::prepare_login(&http.url, oauth_config, &redirect_uri, &client)
        .map_err(|e| anyhow::anyhow!(e))?;
    writeln!(
        stderr,
        "Open this URL in your browser:\n{}",
        login.authorization_url
    )?;
    let _ = open_browser(&login.authorization_url);
    let cancel = Arc::new(AtomicBool::new(false));
    let (tx, rx) = std::sync::mpsc::channel();
    thread::spawn(move || {
        let mut line = String::new();
        if std::io::stdin().read_line(&mut line).is_ok() && !line.trim().is_empty() {
            let _ = tx.send(line);
        }
    });
    let code = oauth::capture_loopback_or_manual_code(
        Some(listener),
        &login.state,
        oauth::LOGIN_WAIT_TIMEOUT,
        &cancel,
        Some(&rx),
    )
    .map_err(|e| anyhow::anyhow!(e))?;
    let response = oauth::exchange_code(
        &login.metadata.token_endpoint,
        &code,
        &login.redirect_uri,
        &login.client_id,
        &login.verifier,
        &login.resource,
        login.client_secret.as_deref(),
        &client,
    )
    .map_err(|e| anyhow::anyhow!(e))?;
    let token = oauth::stored_token_from_response(
        response,
        &login.client_id,
        login.client_secret,
        &http.url,
        Some(login.authorization_server),
        Some(login.metadata.issuer),
        Some(login.metadata.token_endpoint),
        Some(login.resource),
    )
    .map_err(|e| anyhow::anyhow!(e))?;
    oauth::write_token(&paths.root, server, &token).map_err(|e| anyhow::anyhow!(e))?;
    writeln!(stdout, "Authenticated with {server}. Token stored.")?;
    Ok(())
}

pub(crate) fn run_logout(
    server: &str,
    stdout: &mut impl Write,
    _stderr: &mut impl Write,
) -> Result<()> {
    validate_mcp_server_name(server)?;
    let paths = McPaths::resolve()?;
    oauth::delete_token(&paths.root, server).map_err(|e| anyhow::anyhow!(e))?;
    writeln!(stdout, "Logged out from {server}")?;
    Ok(())
}

pub(crate) fn run_test(
    server: &str,
    stdout: &mut impl Write,
    stderr: &mut impl Write,
) -> Result<()> {
    validate_mcp_server_name(server)?;
    let paths = McPaths::resolve()?;
    let settings = read_settings(&paths)?;
    let config = settings
        .mcp_servers
        .get(server)
        .ok_or_else(|| anyhow::anyhow!("MCP server '{server}' is not configured"))?;
    if !config.enabled() {
        anyhow::bail!("MCP server '{server}' is disabled; enable it with /mcp in Mission Control");
    }

    writeln!(stdout, "Testing MCP server: {server}")?;
    if let McpServerConfig::Http(http) = config {
        writeln!(stdout, "  endpoint: {}", sanitized_url(&http.url))?;
    }
    let mut client = connect_selected(server, &settings.mcp_servers, &paths, stderr)?;
    let init = client.initialize().map_err(|error| {
        let _ = writeln!(stderr, "mcp {server}: initialize failed: {error}");
        anyhow::anyhow!(error)
    })?;
    let tools = client.list_tools().map_err(|error| {
        let _ = writeln!(stderr, "mcp {server}: tools/list failed: {error}");
        anyhow::anyhow!(error)
    })?;

    let result = write_connected_diagnostics(stdout, &init, &tools);
    client.shutdown();
    result
}

fn write_connected_diagnostics(
    stdout: &mut impl Write,
    init: &crate::mcp::protocol::InitializeResult,
    tools: &[crate::mcp::protocol::Tool],
) -> Result<()> {
    writeln!(stdout, "Connected")?;
    writeln!(
        stdout,
        "  server: {}",
        sanitize_display_text(&init.server_info.name)
    )?;
    writeln!(
        stdout,
        "  version: {}",
        sanitize_display_text(&init.server_info.version)
    )?;
    writeln!(
        stdout,
        "  protocol: {}",
        sanitize_display_text(&init.protocol_version)
    )?;
    writeln!(stdout, "  tools: {}", tools.len())?;
    for tool in tools {
        let description = sanitize_display_text(tool.description.as_deref().unwrap_or(""));
        let name = sanitize_display_text(&tool.name);
        if description.is_empty() {
            writeln!(stdout, "    - {name}")?;
        } else {
            writeln!(stdout, "    - {name}: {description}")?;
        }
    }
    Ok(())
}

fn connect_selected(
    server: &str,
    settings: &McpServersSettings,
    paths: &McPaths,
    stderr: &mut impl Write,
) -> Result<McpClient> {
    validate_mcp_server_name(server)?;
    let config = settings
        .get(server)
        .expect("server presence validated before connect");
    McpClient::connect_named(Some(server), config, Some(&paths.root)).map_err(|error| {
        let phase = match config {
            McpServerConfig::Stdio(_) => "spawn",
            McpServerConfig::Http(_) => "connect",
        };
        let _ = writeln!(stderr, "mcp {server}: {phase} failed: {error}");
        anyhow::anyhow!(error)
    })
}

fn oauth_status_label(
    paths: &McPaths,
    name: &str,
    config: &McpServerConfig,
    stderr: &mut impl Write,
) -> String {
    let McpServerConfig::Http(http) = config else {
        return String::new();
    };
    if http.oauth.is_none() {
        return String::new();
    }
    match oauth::auth_status(&paths.root, name, &http.url) {
        Ok(status) => format!(", auth: {status}"),
        Err(error) => {
            let _ = writeln!(stderr, "mcp {name}: auth status failed: {error}");
            ", auth: invalid (needs login)".to_string()
        }
    }
}

fn open_browser(url: &str) -> std::io::Result<()> {
    let parsed = reqwest::Url::parse(url).map_err(|_| {
        std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid browser URL")
    })?;
    if !matches!(parsed.scheme(), "http" | "https") || url.contains('\0') {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "browser URL must use HTTP or HTTPS",
        ));
    }
    #[cfg(target_os = "macos")]
    {
        Command::new("open").arg(url).spawn().map(|_| ())
    }
    #[cfg(target_os = "windows")]
    {
        use windows_sys::Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOWNORMAL};
        let url: Vec<u16> = url.encode_utf16().chain(Some(0)).collect();
        let operation: Vec<u16> = "open".encode_utf16().chain(Some(0)).collect();
        // SAFETY: both strings are NUL-terminated and live throughout the call;
        // optional window, parameters, and directory pointers are null.
        let result = unsafe {
            ShellExecuteW(
                std::ptr::null_mut(),
                operation.as_ptr(),
                url.as_ptr(),
                std::ptr::null(),
                std::ptr::null(),
                SW_SHOWNORMAL,
            )
        };
        if result as isize <= 32 {
            Err(std::io::Error::other("could not open browser"))
        } else {
            Ok(())
        }
    }
    #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
    {
        Command::new("xdg-open").arg(url).spawn().map(|_| ())
    }
}

fn sanitized_url(url: &str) -> String {
    match reqwest::Url::parse(url) {
        Ok(parsed) => {
            let host = parsed.host_str().unwrap_or("<unknown>");
            let port = parsed
                .port()
                .map(|port| format!(":{port}"))
                .unwrap_or_default();
            format!("{}://{}{}{}", parsed.scheme(), host, port, parsed.path())
        }
        Err(_) => "<invalid-url>".to_string(),
    }
}