magi-code 0.97.5

Repository-aware CLI coding agent for terminal work
Documentation
use crate::output::sanitize_display_text;
use crate::{
    config::{McPaths, McpServerConfig, read_settings, validate_mcp_server_name},
    mcp::{
        McpClient,
        manager::{McpManager, McpServerStatus},
        oauth,
    },
};
use anyhow::Result;
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: {}",
                    sanitize_display_text(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(sanitized_error)?;
    let login = oauth::prepare_login(&http.url, oauth_config, &redirect_uri, &client)
        .map_err(sanitized_error)?;
    writeln!(
        stderr,
        "Open this URL in your browser:\n{}",
        login.authorization_url
    )?;
    let _ = crate::browser::open_url_in_browser(&login.authorization_url);
    let cancel = Arc::new(AtomicBool::new(false));
    // One manual code is accepted; capacity 1 keeps the reader from blocking on send.
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    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(sanitized_error)?;
    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(sanitized_error)?;
    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(sanitized_error)?;
    oauth::write_token(&paths.root, server, &token).map_err(sanitized_error)?;
    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(sanitized_error)?;
    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 connect_phase = match config {
        McpServerConfig::Stdio(_) => "spawn",
        McpServerConfig::Http(_) => "connect",
    };
    let mut client = McpClient::connect_named(Some(server), config, Some(&paths.root))
        .map_err(|error| server_phase_error(server, connect_phase, &error))?;
    let init = client
        .initialize()
        .map_err(|error| server_phase_error(server, "initialize", &error))?;
    let tools = client
        .list_tools()
        .map_err(|error| server_phase_error(server, "tools/list", &error))?;

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

/// Builds one returned error for a failed MCP phase; server-controlled text is display-sanitized
/// because the top-level error printer writes it directly to the terminal.
fn server_phase_error(server: &str, phase: &str, error: &impl std::fmt::Display) -> anyhow::Error {
    anyhow::anyhow!(
        "mcp {server}: {phase} failed: {}",
        sanitize_display_text(&error.to_string())
    )
}

/// Converts an OAuth error that may embed server-returned text into a display-safe error.
fn sanitized_error(error: impl std::fmt::Display) -> anyhow::Error {
    anyhow::anyhow!(sanitize_display_text(&error.to_string()))
}

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 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: {}",
                sanitize_display_text(&error.to_string())
            );
            ", auth: invalid (needs login)".to_string()
        }
    }
}

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(),
    }
}